What is the MemoryProvider Trait in TinyMemory? The Core Interface for OpenHuman Memory Drivers

The MemoryProvider trait defines the asynchronous contract that every memory driver must implement to expose ingestion, retrieval, and capability discovery to the OpenHuman core, living in the tinymemory_api contract crate to ensure type stability across module boundaries.

The MemoryProvider trait serves as the fundamental abstraction layer within TinyMemory, the memory subsystem of the OpenHuman framework. Defined in the upstream tinymemory_api crate, this trait establishes a minimal, type-stable API that both the core binary and dynamically loaded memory modules compile against. Any driver wishing to integrate with OpenHuman must implement this standardized interface to enable document storage, semantic search, and entity management.

Core Architecture of the MemoryProvider Trait

The trait lives in the tinymemory_api contract crate, a dependency shared by both the host application and loadable modules. This deliberate separation ensures that interface definitions travel across the TinyBus module boundary without exposing internal implementation details like in-process caches or configuration structs.

The design follows a minimal contract philosophy: the trait only contains methods necessary for cross-boundary communication. Everything purely internal remains in the host crate, preventing API bloat while maintaining strict type safety.

Required Methods and Super-Traits

Every memory driver must implement a set of core methods grouped into the base trait and related super-traits. These methods return Result<T, MemoryError> using the crate-wide error type, with most operations being asynchronous.

Synchronous Metadata Methods

Two methods provide immediate, blocking access to driver metadata:

  • driver_id(&self) -> &'static str – Returns a unique static string identifier (e.g., "null" for the built-in fallback).
  • capabilities(&self) -> Capabilities – Returns a synchronous description of supported feature families, including chunks, retrieval, and scoring capabilities.

These synchronous methods allow the host to inspect a driver before initiating expensive async operations.

Asynchronous Operation Methods

All data operations are async and fall into functional families defined by super-traits:

  • ingest(&self, request: IngestRequest) -> Result<IngestResponse> – Pushes new documents, embeddings, or structured data into memory.
  • recall(&self, query: RecallQuery) -> Result<RecallResponse> – Performs read-only retrieval based on semantic or structured queries.
  • Super-trait methods – Additional families like MemoryChunks, MemoryRetrieval, and MemoryScoring provide list_chunks, search_entities, and score_chunks respectively.

ModuleMemoryProvider: The Host Implementation

OpenHuman ships with ModuleMemoryProvider, the concrete implementation located in src/openhuman/modules/memory.rs. This struct acts as the bridge between the core application and dynamically loaded TinyMemory modules.

The implementation forwards every trait method to the loaded module or falls back to the built-in NullMemoryProvider when no module is present. At src/openhuman/modules/memory.rs#L406, the trait implementation delegates calls while preserving the async boundary:

impl MemoryProvider for ModuleMemoryProvider {
    fn driver_id(&self) -> &'static str { self.driver_id }
    fn capabilities(&self) -> Capabilities { self.capabilities.clone() }
    // async methods delegate to the underlying module or null fallback
}

The struct stores the driver identifier reported by the trait:

pub struct ModuleMemoryProvider {
    /// The id reported by `MemoryProvider::driver_id`.
    driver_id: &'static str,
    // ...
}

(see src/openhuman/modules/memory.rs#L207)

Before accepting a module, the host validates advertised capabilities through ModuleMemoryProvider::verify, cross-checking the synchronous capabilities() response against host-side expectations at src/openhuman/modules/memory.rs#L21.

Code Examples: Working with MemoryProvider

Initializing the Default Driver

Create a ModuleMemoryProvider using the standard configuration pattern:

use openhuman::modules::memory::ModuleMemoryProvider;
use std::sync::Arc;
use openhuman::config::Config;

// Build configuration from environment or test fixtures
let config = Arc::new(Config::default());

// Initialize the provider (does not load the module yet)
let provider = ModuleMemoryProvider::new(config);

// Inspect driver metadata
println!("Driver: {}", provider.driver_id());
println!("Capabilities: {:?}", provider.capabilities());

Using the Null Provider for Testing

The NullMemoryProvider offers a lightweight, in-memory implementation for unit testing without external dependencies:

use tinymemory_api::null::NullMemoryProvider;
use openhuman::memory::api::provider::MemoryProvider;
use std::sync::Arc;

// Built-in null driver implements the trait out-of-the-box
let null_provider: Arc<dyn MemoryProvider> = Arc::new(NullMemoryProvider::new());

// Async document insertion
let ingest_req = /* build IngestRequest */;
let resp = null_provider.ingest(ingest_req).await?;
println!("Ingested rows: {}", resp.rows_inserted);

Executing Retrieval Queries

Perform semantic search using the recall method:

use openhuman::memory::api::provider::{MemoryProvider, RecallQuery};

let query = RecallQuery::new("search terms")
    .with_top_k(5);
let result = provider.recall(query).await?;
for hit in result.hits {
    println!("Found: {}", hit.title);
}

Type Stability and Module Boundaries

Because MemoryProvider resides in the contract crate rather than the host application, both the OpenHuman core and any compiled TinyMemory module link against identical trait definitions. This type-stable architecture prevents interface drift that would otherwise cause runtime failures.

Adding new functionality requires releasing a new version of tinymemory_api and updating the corresponding bus contract entry in the module. Without this coordination, calls resolve to Unsupported at runtime, protecting the system from undefined behavior across version mismatches.

Key implementation files include:

Summary

  • The MemoryProvider trait in TinyMemory defines the essential async interface for memory operations including ingest, recall, and capability reporting.
  • Located in the tinymemory_api contract crate, it ensures type stability between the OpenHuman core and dynamically loaded modules.
  • ModuleMemoryProvider in src/openhuman/modules/memory.rs forwards trait implementations to loaded drivers or falls back to NullMemoryProvider.
  • Synchronous methods (driver_id, capabilities) enable immediate introspection, while async methods handle data-intensive operations.
  • The trait's minimal design keeps internal implementation details out of the cross-module contract.

Frequently Asked Questions

What is the difference between MemoryProvider and ModuleMemoryProvider?

MemoryProvider is the abstract trait defining the contract in the tinymemory_api crate, while ModuleMemoryProvider is the concrete host-side implementation in src/openhuman/modules/memory.rs that forwards trait methods to dynamically loaded modules. Think of the trait as the interface specification and ModuleMemoryProvider as the adapter that bridges the host application to external memory drivers.

Why are driver_id and capabilities synchronous while other methods are async?

These methods provide immediate metadata necessary for driver selection and validation before committing to expensive operations. The host calls capabilities() synchronously during ModuleMemoryProvider::verify to confirm feature support without awaiting a runtime, while data operations like ingest and recall are async to accommodate network latency and disk I/O in real storage backends.

How does OpenHuman handle unsupported memory operations?

The trait design uses capability advertisement and runtime error handling. If a module lacks support for a specific operation (or if the bus contract version mismatches), the call resolves to an Unsupported variant of MemoryError. The host validates capabilities upfront through ModuleMemoryProvider::verify, preventing attempts to invoke unimplemented functionality.

Where is the MemoryProvider trait defined versus where it is implemented?

The trait definition lives in the contract crate tinymemory_api at src/openhuman/memory/api/provider.rs, shared between core and modules. Implementations reside in multiple locations: src/openhuman/modules/memory.rs contains the forwarding ModuleMemoryProvider, while src/openhuman/modules/memory_part_01.rs through memory_part_03.rs contain the specific family implementations. The built-in NullMemoryProvider offers a reference implementation for testing environments.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →