Building Memory-Enabled AI Agents Using Memori: Implementation Guide with awesome-ai-apps
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 and orchestrated through the Streamlit interface in 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 at lines 41-84, the init_memori_with_nebius() function creates a SQLite-backed Memori instance and registers it with an OpenAI-compatible client:
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:
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 at lines 180-190, the application searches Memori using mem.search():
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 file implements a MemoriManager class that abstracts the same initialization pattern for educational use cases. Similarly, 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:
- Copy the
init_memori_with_nebius()pattern fromcore.py - Implement a data fetcher for your specific domain (database, API, or file system)
- Create an ingestion function that formats data and calls the LLM with storage prompts
- 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 incore.pydemonstrates 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →