# Building Memory-Enabled AI Agents Using Memori: Implementation Guide with awesome-ai-apps

> Build memory enabled AI agents with Memori v3. Ingest data, query stored memories with LLMs, and ground responses in historical context. Implement today.

- Repository: [Arindam Majumder /awesome-ai-apps](https://github.com/Arindam200/awesome-ai-apps)
- Tags: how-to-guide
- Published: 2026-05-06

---

**You can build persistent AI agents by registering an OpenAI-compatible LLM client with Memori v3, ingesting domain data via lightweight chat prompts, and querying stored memories at runtime to ground responses in historical context.**

The **awesome-ai-apps** repository demonstrates a production-ready pattern for building memory-enabled AI agents that persist interactions across sessions. By combining **Memori v3**—a long-term vector-store memory—with any OpenAI-compatible LLM backend, developers can create agents that remember past conversations and domain knowledge. This guide breaks down the exact implementation used in the YouTube Trend Analysis Agent to show you how to apply this architecture to your own projects.

## The Three-Step Architecture for Memory-Enabled Agents

The repository follows a consistent three-step pattern for building memory-enabled AI agents using Memori. Each step is implemented in [`memory_agents/youtube_trend_agent/core.py`](https://github.com/Arindam200/awesome-ai-apps/blob/main/memory_agents/youtube_trend_agent/core.py) and orchestrated through the Streamlit interface in [`app.py`](https://github.com/Arindam200/awesome-ai-apps/blob/main/app.py).

### Step 1: Initialize Memori with an OpenAI-Compatible Client

The foundation of the pattern involves wiring Memori to automatically capture LLM interactions. In [`core.py`](https://github.com/Arindam200/awesome-ai-apps/blob/main/core.py) at lines 41-84, the `init_memori_with_nebius()` function creates a SQLite-backed Memori instance and registers it with an OpenAI-compatible client:

```python
def init_memori_with_nebius() -> Memori | None:
    base_url = os.getenv("OPENAI_BASE_URL", "https://api.minimax.io/v1")
    api_key   = os.getenv("OPENAI_API_KEY", "")
    if not api_key:
        st.warning("OPENAI_API_KEY is not set – Memori v3 ingestion will not be active.")
        return None
    # Create a SQLite engine for persistence

    engine = create_engine(f"sqlite:///{os.getenv('SQLITE_DB_PATH','./memori.sqlite')}",
                           connect_args={"check_same_thread": False})
    SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
    client = OpenAI(base_url=base_url, api_key=api_key)
    mem = Memori(conn=SessionLocal).openai.register(client)   # ← registers client

    mem.attribution(entity_id="youtube-channel", process_id="youtube-trend-agent")
    mem.config.storage.build()
    st.session_state.memori = mem
    st.session_state.nebius_client = client
    return mem

```

The `Memori(conn=SessionLocal).openai.register(client)` call at line 78 is critical—it intercepts every subsequent `client.chat.completions.create()` call and stores the request-response pair as a memory entry.

### Step 2: Ingest Domain-Specific Data into Memory

Once initialized, the agent ingests external data by formatting it as documents and sending "store this" prompts to the LLM. Because Memori is already registered, these ingestion calls automatically persist without explicit database writes.

The `ingest_channel_into_memori()` function (lines 236-267) demonstrates this by processing YouTube videos:

```python
def ingest_channel_into_memori(channel_url: str) -> int:
    memori = st.session_state.get("memori") or init_memori_with_nebius()
    client = st.session_state.get("nebius_client")
    videos = fetch_channel_videos(channel_url)
    for video in videos:
        doc = f"""YouTube Video
Channel URL: {channel_url}
Title: {video["title"]}
Video URL: {video["url"]}
Published at: {video["published_at"]}
Views: {video["views"]}
Duration (seconds): {video["duration_seconds"]}
Topics: {", ".join(video["topics"])} 
Description: {video["description"][:1000]}
"""
        client.chat.completions.create(
            model=os.getenv("YOUTUBE_TREND_INGEST_MODEL", "MiniMax-M2.1"),
            messages=[{"role": "user",
                       "content": f"Store the following YouTube video metadata in memory.\n\n{doc}"}],
        )
    return len(videos)

```

The `fetch_channel_videos()` helper (lines 96-122) uses `yt-dlp` to scrape metadata, which is then formatted into textual documents. Each `client.chat.completions.create()` call triggers Memori's automatic storage mechanism defined in lines 78-84.

### Step 3: Query Memories at Runtime to Ground Responses

When users submit queries, the agent retrieves relevant historical context before generating responses. In [`app.py`](https://github.com/Arindam200/awesome-ai-apps/blob/main/app.py) at lines 180-190, the application searches Memori using `mem.search()`:

```python
mem = st.session_state.get("memori")
memori_context = ""
if mem and hasattr(mem, "search"):
    results = mem.search(user_prompt, limit=5)
    if results:
        memori_context = "\n\nRelevant snippets from your channel history:\n" + "\n".join(f"- {r}" for r in results)

full_prompt = f"""You are a YouTube strategy assistant analyzing the channel.
...
User question:
{user_prompt}

Memory context (may be partial):
{memori_context}
"""
response = st.session_state.openai_client.chat.completions.create(
    model=os.getenv("YOUTUBE_TREND_MODEL", "MiniMax-M2.1"),
    messages=[{"role":"system","content":"You are a YouTube strategy assistant ..."},
              {"role":"user","content": full_prompt}],
)

```

The retrieved snippets are merged into a composite system prompt that combines live data with historical memory, enabling the LLM to reason over both current and past information.

## Key Architectural Components

Several design decisions in the awesome-ai-apps repository make this pattern robust and reusable.

**SQLite Persistence**: Memori uses a local SQLite database configured via `SQLITE_DB_PATH`, with SQLAlchemy session management ensuring thread safety across Streamlit interactions (lines 88-90).

**Provider Agnosticism**: The implementation accepts any OpenAI-compatible endpoint through `OPENAI_BASE_URL` and `OPENAI_API_KEY` environment variables (lines 54-56), allowing seamless swapping between MiniMax, Nebius, or OpenAI models without code changes.

**Session State Management**: Streamlit's `st.session_state` caches both the `Memori` instance and the `nebius_client` to maintain persistent connections across user interactions, preventing memory leaks and costly re-initializations.

## Extending the Pattern to New Domains

The repository proves this architecture scales beyond YouTube analysis. The [`memory_agents/study_coach_agent/memory_utils.py`](https://github.com/Arindam200/awesome-ai-apps/blob/main/memory_agents/study_coach_agent/memory_utils.py) file implements a `MemoriManager` class that abstracts the same initialization pattern for educational use cases. Similarly, [`memory_agents/social_media_agent/twitter_agents.py`](https://github.com/Arindam200/awesome-ai-apps/blob/main/memory_agents/social_media_agent/twitter_agents.py) adapts the ingestion pipeline for Twitter data.

To build your own memory-enabled AI agent using Memori:

1. Copy the `init_memori_with_nebius()` pattern from [`core.py`](https://github.com/Arindam200/awesome-ai-apps/blob/main/core.py)
2. Implement a data fetcher for your specific domain (database, API, or file system)
3. Create an ingestion function that formats data and calls the LLM with storage prompts
4. Query `mem.search()` before every LLM call to inject relevant historical context

## Summary

- **Memori v3** provides persistent vector-store memory through automatic interception of OpenAI-compatible client calls.
- The `init_memori_with_nebius()` function in [`core.py`](https://github.com/Arindam200/awesome-ai-apps/blob/main/core.py) demonstrates the essential setup: SQLite initialization, client registration, and session state caching.
- Data ingestion works by sending formatted documents through standard LLM chat completions, with Memori capturing the interaction automatically.
- Runtime queries use `mem.search(prompt, limit=5)` to retrieve relevant historical snippets that ground the LLM's responses in accumulated knowledge.
- The pattern is domain-agnostic, as evidenced by implementations for YouTube trends, study coaching, and social media analysis within the awesome-ai-apps repository.

## Frequently Asked Questions

### What is Memori and how does it differ from standard conversation history?

Memori is a long-term vector-store memory system that persists beyond single sessions. Unlike standard conversation history that tracks only the current chat thread, Memori stores structured embeddings of all interactions, allowing agents to retrieve semantically relevant information from previous conversations or data ingestion cycles using vector similarity search.

### Can I use Memori with LLM providers other than MiniMax?

Yes. The awesome-ai-apps implementation uses standard OpenAI SDK patterns with configurable `base_url` and `api_key` parameters. You can substitute MiniMax with any OpenAI-compatible provider—including Nebius, Azure OpenAI, or local LLM servers—by setting the appropriate environment variables without modifying the core Memori initialization logic.

### How does the automatic memory capture work technically?

When you call `Memori(conn=SessionLocal).openai.register(client)`, Memori wraps the client's `chat.completions.create` method. Every subsequent API call is intercepted, embedded, and stored in the SQLite database along with metadata. This happens transparently to your application code, requiring no explicit `save()` or `insert()` calls during data ingestion.

### What are the performance implications of querying Memori at runtime?

The implementation uses `mem.search(prompt, limit=5)` to retrieve only the top-5 most relevant snippets, keeping latency minimal. Since Memori v3 uses SQLite with local vector storage, searches execute without network overhead. For high-throughput applications, you can increase the `limit` parameter or implement caching layers on top of the Streamlit session state pattern shown in lines 88-90 of [`core.py`](https://github.com/Arindam200/awesome-ai-apps/blob/main/core.py).