plugins — Plugin System & Memory Backends
Plugin System & Memory Backends
Dynamic plugin discovery and loading infrastructure for context engines and memory providers. YAML-manifest-driven architecture with 8 hot-swappable memory backends, from local vector stores to cloud APIs.
🎯 Why This Module Exists
Without the plugins module, every memory backend would need to be hard-coded into the agent’s core initialization path. Adding a new memory provider—say, a knowledge-graph backend or a cloud-hosted vector store—would require modifying the agent entry point, importing heavyweight SDKs unconditionally, and redeploying the entire system. The plugins module decouples what backends exist from which backend is active. It provides a filesystem-based discovery mechanism that scans for plugin.yaml manifests at startup, presents available options to the CLI without importing anything, and only loads the one backend selected in config.yaml. This lazy-loading architecture means the agent starts quickly even when 8 backends are installed, and users can swap memory strategies—from Holographic’s local embeddings to Honcho’s dialectic modeling to Mem0’s external service—with a single config change and zero code modifications. The plugin system also isolates dependency failures: if Honcho’s SDK is missing, that provider shows as “unavailable” but every other backend continues to work. This is essential for a tool that runs across vastly different environments, from GPU-equipped research servers to lightweight CI containers.
💡 Real-world analogy
Think of the plugins module as a universal power strip with interchangeable adapters. The agent is the appliance that needs power (memory), and each backend is a different country’s plug format. The plugin system is the adapter mechanism: it inspects each adapter’s label (the plugin.yaml manifest) to know what’s available, but only clicks in the one adapter you’ve selected. You can swap adapters without rewiring the appliance, and a broken adapter doesn’t prevent the others from working.
🔵 Plugin System 🟢 8 Memory Backends 🟠 YAML Manifest 🔴 Hot-Swappable
Architecture
The plugin system follows a registry + lazy loading pattern. At startup, the discovery layer scans the filesystem for plugin.yaml manifests in each backend directory. Only the backend selected in config.yaml is actually imported via Python’s importlib machinery. This keeps startup fast and avoids pulling in heavyweight SDK dependencies for unused backends.
flowchart LR
subgraph Discovery["Discovery Layer"]
ConfigYAML["config.yaml
memory.provider = X"] --> Scanner["discover_memory_providers()"]
Scanner --> PluginYAML["plugin.yaml
per backend"]
end
subgraph Loading["Loading Layer"]
PluginYAML --> Loader["load_memory_provider(name)"]
Loader --> ImportLib["importlib.util
spec_from_file_location()"]
ImportLib --> Register["register() pattern
OR subclass detection"]
end
subgraph Backends["Memory Backends"]
Register --> Holo["holographic/
Local Vector Store"]
Register --> Honcho["honcho/
Dialectic Modeling"]
Register --> Super["supermemory/
Cloud API"]
Register --> Others["hindsight / byterover
openviking / mem0 / retaindb"]
end
Data Flow
When the agent needs to store or recall a memory, the request flows through a well-defined chain: from the agent’s memory manager, through the plugin loader, into the active backend, and down to the storage layer. Only one backend is active at any time, selected at startup.
flowchart TD Agent["AIAgent
Initialization"] --> Config["Read config.yaml
memory.provider"] Config --> Discover["discover_memory_providers()
Scan filesystem + plugin.yaml"] Discover --> Check{"Provider
available?"} Check -->|Yes| Load["load_memory_provider(name)
importlib dynamic import"] Check -->|No| Warn["logger.warning()
Provider unavailable"] Load --> Dual{"Detection
mode"} Dual -->|register()| Collector["_ProviderCollector
captures instance"] Dual -->|subclass| Inspect["Find MemoryProvider
subclass in module"] Collector --> Instance["MemoryProvider
Instance"] Inspect --> Instance Instance --> Store["store(key, value)
Save memory"] Instance --> Recall["recall(query)
Retrieve memory"] Store --> Backend["Active Backend
Storage Layer"] Recall --> Backend
🚶 Step-by-Step Narrated Walkthrough
Follow a single memory save-and-recall operation as it flows through the entire plugins pipeline:
- Agent Startup —
AIAgent.__init__()readsconfig.yamland findsmemory.provider: holographic. It callsload_memory_provider("holographic"). - Discovery Scan —
discover_memory_providers()walksplugins/memory/, finding 8 subdirectories. For each, it readsplugin.yamlto extract the name, description, and availability flag—without importing any Python code. - Availability Check — The holographic backend’s
plugin.yamldeclares no external dependencies, so it passes the availability check. Backends likehonchoormem0would check for API keys in environment variables. - Dynamic Import —
importlib.util.spec_from_file_location()creates a module spec forplugins/memory/holographic/__init__.py. The loader registers parent packages insys.modulesfirst, enabling relative imports within the plugin. - Submodule Registration — Before executing the main module, the loader pre-registers all
.pyfiles in the holographic directory (store.py,retrieval.py,holographic.py) so thatfrom .store import MemoryStoreworks correctly. - Provider Extraction — The loader first tries
mod.register(collector)—if the module uses the register pattern, the_ProviderCollectorcaptures the instance. If that fails, it falls back to scanning the module for aMemoryProvidersubclass and instantiating it directly. - Instance Ready — The
HolographicMemoryinstance is returned to the agent. It holds a local vector store, an embedding model reference, and retrieval configuration. - Memory Save — During a conversation, the agent calls
provider.store("user prefers dark mode", embedding). The holographic backend computes an embedding vector, adds it to the local FAISS-like index, and persists the mapping to disk. - Memory Recall — Later, the agent calls
provider.recall("what theme does the user like?"). The holographic backend embeds the query, performs cosine similarity search against the stored vectors, and returns the top-k matches ranked by relevance. - CLI Discovery — Meanwhile,
discover_plugin_cli_commands()checks only the active provider’scli.py. If holographic has CLI commands (e.g.,hermes memory list), they’re registered into the main argparse tree. Unused providers’ CLI modules are never imported.
🏗️ Design Decisions
| Decision | Choice Made | Alternatives Considered | Why This Approach |
|---|---|---|---|
| Plugin metadata format | YAML manifests (plugin.yaml) |
Python decorators, setup.py entry points, JSON config |
YAML can be read without importing Python code—critical for fast CLI help and discovery. Entry points require pip installation; decorators require import. |
| Plugin discovery strategy | Eager discovery, lazy loading | Lazy discovery (scan on first use), eager loading (import all at startup) | Eager discovery is cheap (filesystem scan + YAML parse). Lazy loading avoids importing heavy SDKs (Honcho, Mem0) for backends that aren’t active. Best of both worlds. |
| Backend abstraction | MemoryProvider base class + dual detection (register / subclass) |
Single registration mode, ABC-only, duck typing | Dual detection maximizes compatibility. Old plugins use register(ctx) matching the general plugin system; new plugins use direct subclassing. Neither breaks the other. |
| Single-active pattern | Only ONE memory provider active at runtime | Multiple concurrent providers, chained providers, priority cascade | Agent initialization needs deterministic behavior. Mixing strategies would complicate the API surface and create ambiguity about which backend handles a given query. Config clarity beats flexibility here. |
| Holographic as local-first default | Zero-dependency local vector store as the default backend | Cloud-first default (Supermemory), no default | Local-first ensures the agent works out of the box without API keys or network access. Users can opt into cloud backends when they need cross-device sync or larger scale. |
| CLI command scoping | Only the active provider’s CLI commands are registered | Register all providers’ CLI commands, namespace by provider name | Avoids conflicting commands (e.g., two providers both defining memory list). Keeps CLI clean and avoids importing SDKs for unused providers. |
| sys.modules pre-registration | Register parent packages and sibling modules in sys.modules before exec_module() |
Require absolute imports only, use __import__ |
Plugins need relative imports (from .store import MemoryStore) to work naturally. Pre-registration prevents ImportError loops when submodules reference each other. |
🔬 Algorithm Spotlight
Holographic Vector Similarity Retrieval
The Problem: When the agent needs to recall past interactions, it must find the most semantically relevant memories from potentially thousands of stored entries. Simple keyword matching fails because natural language queries rarely use the same words as stored memories (“what theme does the user like?” vs. “user prefers dark mode”).
The Approach: Holographic converts both stored memories and recall queries into dense embedding vectors using a local embedding model. At recall time, it computes the cosine similarity between the query vector and every stored vector, then returns the top-k results above a configurable similarity threshold. The local vector index is organized for batch retrieval, allowing the agent to fetch related memories in a single pass.
Why This Algorithm: Cosine similarity on dense embeddings captures semantic meaning that keyword search misses. Alternatives like BM25 (term frequency) would fail on paraphrased content. Full-text search with stemming would miss conceptual similarity. The trade-off is memory: each embedding vector requires storage proportional to the model’s hidden dimension (typically 384–768 floats). For a local-first backend, this is acceptable—even 100K memories at 768 dimensions is only ~300 MB.
graph TD
A["Query: 'what theme?'"] --> B["Embed query
→ vector q"]
B --> C{"Index size
> threshold?"}
C -->|"Small (< 10K)"| D["Brute-force cosine
similarity scan"]
C -->|"Large (10K+)"| E["Approximate NN
with HNSW index"]
D --> F["Rank by similarity"]
E --> F
F --> G{"Score >
min_threshold?"}
G -->|Yes| H["Return top-k
memories"]
G -->|No| I["Return empty
(no relevant memories)"]
Trace through an example
Given: 3 stored memories: “user prefers dark mode” (vec A), “last session discussed Python” (vec B), “user’s timezone is PST” (vec C). Query: “what theme does the user like?”
Step 1: Embed the query into vector q. The embedding model maps “theme” and “dark mode” into nearby regions of the vector space.
Step 2: Compute cosine similarity: sim(q, A) = 0.87, sim(q, B) = 0.23, sim(q, C) = 0.31. Memory A scores highest because “theme” and “dark mode” are semantically linked.
Step 3: With min_threshold = 0.5 and top_k = 3, only memory A (0.87) passes. Memories B and C are filtered out.
Result: Returns “user prefers dark mode” with similarity 0.87. A naive keyword search for “theme” would return nothing because no stored memory contains the word “theme.”
⚠️ What Could Go Wrong
| Failure Mode | Trigger | Detection | Recovery / Mitigation |
|---|---|---|---|
| Missing API key for cloud backend | User selects honcho or mem0 provider but env var (HONCHO_API_KEY, MEM0_API_KEY) is unset |
discover_memory_providers() checks availability flags in plugin.yaml and env vars; marks provider as unavailable |
Provider appears as “unavailable” in CLI listing. Agent falls back to warning log. No crash—user sees actionable message about which env var to set. |
| Malformed plugin directory | A backend directory exists but lacks __init__.py or has syntax errors in its Python files |
discover_*() wraps each directory scan in try/except; skips broken directories with logger.warning() |
Graceful degradation: the broken backend is skipped, other backends remain available. No crash at startup. |
| Relative import failure inside plugin | A plugin uses from .store import X but sys.modules pre-registration missed a submodule or ran in wrong order |
ImportError during spec.loader.exec_module() |
The entire plugin fails to load with a clear traceback. The sys.modules pre-registration step was specifically designed to prevent this, but edge cases (circular imports, conditional imports) can still trigger it. |
| Dual-mode detection fails | Plugin has neither a register() function nor a MemoryProvider subclass (malformed plugin) |
load_memory_provider() returns None after both detection paths fail |
Agent logs a warning identifying the plugin by name. User sees “could not load provider X” and can switch to a working backend via config. |
| Stale plugin.yaml metadata | Developer adds a new dependency to a backend but forgets to update plugin.yaml availability flags |
Discovery reports the provider as “available” but loading fails when the missing dependency is imported | Load-time error is caught and logged. The mismatch between discovery and loading is a known trade-off of the eager-discover/lazy-load pattern. Fix: update plugin.yaml. |
⚡ Performance Profile
Hot Paths
The most performance-sensitive code paths in this module:
- Plugin Discovery: Filesystem walk + YAML parse — O(n) in number of backend directories. Typically < 10ms for 8 backends. This runs once at startup and is intentionally cheap.
- Dynamic Import:
importlib.util.spec_from_file_location()+exec_module()— 50–200ms depending on backend complexity and SDK import time. Only runs for the active provider. - Holographic Recall: Embedding computation + vector similarity search — O(d×n) for brute-force where d = embedding dimension, n = stored memories. Dominant cost for large memory stores.
- CLI Command Discovery: Imports only
cli.py(not the full backend SDK) — keeps CLI startup under 100ms even for backends with heavy dependencies.
Resource Usage
- Memory: Only the active backend’s modules are loaded into memory. Holographic additionally allocates a vector index proportional to stored memories (~300 bytes per memory entry at 768 dimensions). Cloud backends (supermemory, mem0) have minimal local memory footprint.
- I/O: Discovery reads
plugin.yamlfiles from disk (one per backend, ~100 bytes each). Holographic reads/writes its vector index file on store/recall. Cloud backends make HTTP requests per operation.config.yamlis read once at startup to determine the active provider.
Key Components
| File | Role | Key Exports |
|---|---|---|
plugins/__init__.py | Top-level plugin discovery and loading orchestrator | find_plugins(), load_plugin() |
plugins/context_engine/__init__.py | Context engine plugin interface and discovery | discover_context_engines(), load_context_engine() |
plugins/memory/__init__.py | Memory provider discovery, loading, and CLI bridge | discover_memory_providers(), load_memory_provider(), discover_plugin_cli_commands() |
plugins/memory/holographic/__init__.py | Local vector store memory backend entry point | HolographicMemory (MemoryProvider subclass) |
plugins/memory/holographic/store.py | Vector index persistence and CRUD operations | MemoryStore |
plugins/memory/holographic/retrieval.py | Embedding-based similarity search and ranking | retrieve(), cosine similarity utilities |
plugins/memory/honcho/__init__.py | Plastic Labs dialectic user modeling backend | HonchoMemory (MemoryProvider subclass) |
plugins/memory/honcho/client.py | Honcho API client wrapper | HonchoClient |
plugins/memory/honcho/session.py | Session management for dialectic modeling | HonchoSession |
plugins/memory/honcho/cli.py | CLI subcommands for Honcho management | register_cli(subparser) |
plugins/memory/supermemory/ | Cloud-based memory API backend | SupermemoryProvider |
plugins/memory/hindsight/ | Knowledge graph memory backend | HindsightMemory |
plugins/memory/byterover/ | Byte-level retrieval memory backend | ByteRoverMemory |
plugins/memory/openviking/ | Open-source memory backend | OpenVikingMemory |
plugins/memory/mem0/ | External memory service integration | Mem0Memory |
plugins/memory/retaindb/ | Database-backed persistent memory | RetainDBMemory |
Sub-Modules
🔌 Core Plugin System
Discovery and loading infrastructure shared by all plugin types. Handles filesystem scanning, YAML manifest parsing, importlib-based dynamic loading, and the dual register()/subclass detection pattern.
plugins/__init__.py— Top-level orchestratorplugins/context_engine/__init__.py— Context engine interfaceplugins/memory/__init__.py— Memory provider interface_EngineCollector/_ProviderCollectorhelpers
💎 holographic/
Local-first vector store memory backend. Embeds memories as dense vectors and retrieves them via cosine similarity. Zero external dependencies—works offline, making it the default backend.
holographic.py— Main provider implementationstore.py— Vector index persistenceretrieval.py— Similarity search engineplugin.yaml— Metadata manifest
🗣 honcho/
Dialectic user modeling via Plastic Labs’ Honcho platform. Builds a dynamic model of user preferences and personality through multi-turn conversation analysis. Requires HONCHO_API_KEY.
client.py— Honcho API clientsession.py— Session lifecycle managementcli.py— CLI subcommands (hermes memory honcho ...)plugin.yaml— Availability flags
☁️ Other Backends
Six additional memory backends covering cloud APIs, knowledge graphs, byte-level retrieval, and database storage. Each follows the same plugin.yaml + __init__.py pattern.
supermemory/— Cloud-hosted memory APIhindsight/— Knowledge graph relationshipsbyterover/— Byte-level retrieval engineopenviking/— Open-source memory backendmem0/— External memory serviceretaindb/— Database-backed persistence
How plugins Connects to the Agent Core
The plugins module sits between the agent’s configuration system and its runtime memory operations. The config system determines which provider to load; the agent core consumes the loaded provider as a tool for store/recall operations.
sequenceDiagram
participant Config as hermes_cli.config
participant Plugins as plugins/memory
participant Agent as AIAgent
participant Backend as Active Backend
Config->>Plugins: memory.provider = "holographic"
Plugins->>Plugins: discover_memory_providers()
Plugins->>Plugins: load_memory_provider("holographic")
Plugins-->>Agent: HolographicMemory instance
Agent->>Backend: store(key, value)
Backend-->>Agent: OK
Agent->>Backend: recall(query)
Backend-->>Agent: top-k memories
Key insight: The plugins module owns discovery and loading. The agent core owns when and what to memorize. The active backend owns how memories are stored and retrieved. These three concerns are cleanly separated by the MemoryProvider interface.
Directory Map
Source tree for plugins
plugins/
├── __init__.py ← Top-level plugin discovery & loading
├── context_engine/ ← Context compression backends
│ └── __init__.py ← discover/load context engines
├── memory/ ← Memory provider backends
│ ├── __init__.py ← discover/load memory providers, CLI bridge
│ ├── holographic/ ← Local vector store (default)
│ │ ├── __init__.py ← HolographicMemory provider
│ │ ├── holographic.py ← Core implementation
│ │ ├── store.py ← Vector index persistence
│ │ ├── retrieval.py ← Cosine similarity search
│ │ └── plugin.yaml ← Metadata manifest
│ ├── honcho/ ← Dialectic user modeling
│ │ ├── __init__.py ← HonchoMemory provider
│ │ ├── client.py ← Honcho API client
│ │ ├── session.py ← Session management
│ │ ├── cli.py ← CLI subcommands
│ │ └── plugin.yaml ← Metadata + availability flags
│ ├── supermemory/ ← Cloud memory API
│ │ ├── __init__.py ← SupermemoryProvider
│ │ └── plugin.yaml
│ ├── hindsight/ ← Knowledge graph memory
│ │ ├── __init__.py ← HindsightMemory provider
│ │ └── plugin.yaml
│ ├── byterover/ ← Byte-level retrieval
│ │ ├── __init__.py ← ByteRoverMemory provider
│ │ └── plugin.yaml
│ ├── openviking/ ← Open-source memory
│ │ ├── __init__.py ← OpenVikingMemory provider
│ │ └── plugin.yaml
│ ├── mem0/ ← External memory service
│ │ ├── __init__.py ← Mem0Memory provider
│ │ └── plugin.yaml
│ └── retaindb/ ← Database-backed memory
│ ├── __init__.py ← RetainDBMemory provider
│ └── plugin.yaml