How OpenHuman's Memory Subsystem Integrates TinyCortex and TinyMemory

OpenHuman routes all agent learning through a three-tier memory architecture where TinyMemory provides the contract layer (API types and traits) and TinyCortex implements the engine layer (SQLite-backed vector storage), mediated by policy enforcement code in src/openhuman/memory/guard.rs.

OpenHuman stores conversations, documents, goals, and tool memories in a modular subsystem that decouples policy from implementation. This design allows developers to swap vector store backends without modifying host logic. The architecture separates concerns into distinct layers: the host layer manages RPC endpoints and security, the contract layer defines shared types, and the engine layer handles persistence.

Three-Layer Architecture

The subsystem organizes functionality into clear separation layers to maintain stability across the codebase.

Host Layer: Policy and RPC Surface

The host layer lives in src/openhuman/memory/* and exposes JSON-RPC endpoints such as memory_ingest and memory_query. This layer applies taint rules, budget constraints, and PII scrubbing before any data reaches the storage engine. The driver selection logic in src/openhuman/memory/driver.rs determines which backend fulfills requests, defaulting to "tinycortex".

Contract Layer: TinyMemory API

TinyMemory (the tinymemory_api crate) defines the wire-format contract between the host and any loaded modules. It exports core types like MemoryCategory, RecallOpts, and MemoryIngestionRequest, which are re-exported in src/openhuman/memory/mod.rs for host consumption. The trait tinymemory_api::traits::Memory serves as the seam that all providers must implement.

Engine Layer: TinyCortex Implementation

TinyCortex (tinycortex::memory::*) provides the concrete implementation of the contract. It manages the SQLite-backed vector store, summarisation trees, chunking pipelines, and embedding generation. The host never imports engine types directly; it communicates solely through the TinyMemory trait, enabling drop-in replacements.

Request Flow Through the Subsystem

Every memory operation traverses a six-step pipeline that enforces security before persistence:

  1. JSON-RPC request arrives at the OpenHuman core (e.g., memory_ingest or memory_query).
  2. Dispatch occurs in src/openhuman/memory/ops.rs, which implements the RPC handlers and delegates to the driver.
  3. Trait invocation happens through tinymemory_api::traits::Memory, ensuring the host remains agnostic to the specific engine.
  4. Driver binding in src/openhuman/memory/driver.rs routes calls to tinycortex::memory::MemoryConfig by default.
  5. Guard enforcement in src/openhuman/memory/guard.rs validates taint/scope permissions, token budgets, and scrubs PII via the safety module.
  6. Engine execution writes to the SQLite vector database, updates the summarisation tree, and returns results back through the guard to the RPC layer.

Security Guard and Policy Enforcement

The GuardedMemory wrapper in src/openhuman/memory/guard.rs intercepts every provider call to enforce three critical policies:

  • Taint and Scope Validation: Ensures the current turn only accesses data from permitted sources (workspace, vault, etc.) defined in SourceScope.
  • Budget Enforcement: Tracks token and embedding consumption against user-defined limits.
  • PII Scrubbing: The sanitize_for_storage function in the safety module removes secrets before data persists to disk.

Working with Memory: Code Examples

Ingesting Documents

To ingest content into the memory subsystem, invoke the RPC handler with a MemoryIngestionRequest:

use openhuman::memory::ops::*;
use openhuman::memory::api::types::{MemoryIngestionRequest, ExtractionMode};

let request = MemoryIngestionRequest {
    user_id: user.id(),
    mode: ExtractionMode::Auto,
    inputs: vec![/* document blobs */],
    ..Default::default()
};

// Routes through driver -> TinyCortex engine
let outcome = rpc_ingest(&core_context, request).await?;
println!("Ingested {} chunks", outcome.chunks_added);

Source: src/openhuman/memory/ops.rs – rpc_ingest

Querying Memory

Retrieve relevant context using RecallOpts through the unified query interface:

use openhuman::memory::ops::*;
use openhuman::memory::api::types::RecallOpts;

let opts = RecallOpts {
    query: "What did the user say about project X?".into(),
    limit: 5,
    ..Default::default()
};

let result = rpc_query(&core_context, opts).await?;
for (doc, score) in result.matches {
    println!("score={:.2} → {}", score, doc.title);
}

Source: src/openhuman/memory/ops.rs – rpc_query

Extending OpenHuman with Custom Memory Providers

You can replace TinyCortex by implementing the Memory trait from the contract layer. Register your provider in src/openhuman/memory/driver.rs to enable selection via configuration:

use tinymemory_api::traits::Memory;
use tinymemory_api::types::{MemoryIngestionRequest, RecallOpts, IngestionOutcome, QueryOutcome, MemoryError};

#[derive(Clone)]
pub struct MyCustomProvider;

impl Memory for MyCustomProvider {
    async fn ingest(&self, req: MemoryIngestionRequest) -> Result<IngestionOutcome, MemoryError> {
        // Custom storage logic
        Ok(IngestionOutcome::default())
    }

    async fn recall(&self, opts: RecallOpts) -> Result<QueryOutcome, MemoryError> {
        // Custom retrieval logic
        Ok(QueryOutcome::default())
    }
}

After registering in the driver map, set your provider name in the workspace configuration to activate it.

Summary

  • OpenHuman's memory subsystem uses a three-layer architecture separating host policy (src/openhuman/memory/), API contracts (tinymemory_api), and engine implementation (tinycortex).
  • TinyMemory provides the trait and type definitions that decouple the host from specific storage engines.
  • TinyCortex serves as the default engine, offering SQLite-backed vector storage and summarisation trees.
  • Security enforcement occurs in src/openhuman/memory/guard.rs, applying taint checks, budget limits, and PII scrubbing before persistence.
  • Extensibility is built-in: implement tinymemory_api::traits::Memory and register in driver.rs to swap storage backends without changing RPC handlers.

Frequently Asked Questions

What is the difference between TinyMemory and TinyCortex?

TinyMemory is the contract layer (tinymemory_api crate) that defines shared types and the Memory trait. TinyCortex is the engine layer that implements that trait using SQLite vector stores and tree summarisation. TinyMemory enables the host to communicate with any engine; TinyCortex is the specific engine used by default.

How does OpenHuman enforce security policies on memory operations?

All provider calls pass through src/openhuman/memory/guard.rs, where the GuardedMemory wrapper enforces taint/scope permissions, token budgets, and embedding limits. The safety module scrubs PII before data reaches the storage driver, ensuring secrets never persist to disk.

Can I replace TinyCortex with my own vector database implementation?

Yes. Implement the tinymemory_api::traits::Memory trait in your custom provider, then register it in src/openhuman/memory/driver.rs. The host communicates exclusively through the trait interface, so no changes are required in ops.rs or other host modules when swapping engines.

Where are the RPC handlers for memory operations defined?

The JSON-RPC handlers for memory_ingest, memory_query, and related endpoints live in src/openhuman/memory/ops.rs. These functions parse requests, enforce guards, and delegate to the selected driver, which forwards calls to TinyCortex or your custom provider.

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 →