In the previous chapter, LLM Provider Abstraction, we gave our bot a "universal brain" capable of thinking by connecting to models like GPT-4 or Claude.
However, currently, our bot suffers from a severe case of Amnesia.
If you tell the bot: "My name is Alice." And then restart the bot and ask: "Who am I?" The bot will reply: "I don't know."
This is because LLMs are stateless. They treat every request as a brand-new interaction. In this chapter, we will fix this by building the Memory System.
To make our bot feel "alive," we need to store information. In nanobot, we split memory into two distinct types, just like humans do.
Imagine you are in a meeting. You take quick notes on a Notebook to remember what was said 5 minutes ago.
sessions/telegram_123.jsonlAt the end of the day, you summarize the important facts into a Journal. "Today I met Alice. She is a Python developer."
memory/MEMORY.mdLet's start with the "Notebook." We need to record every message that comes in (from the User) and every message that goes out (from the Bot).
We use a format called JSONL. It is simply a file where every line is a valid JSON object.
Why JSONL? It is "Append-Only." We never have to rewrite the whole file; we just add a new line to the bottom. This makes it very fast and safe.
In nanobot/session/manager.py, we define what a Session looks like.
# nanobot/session/manager.py
@dataclass
class Session:
key: str # e.g., "telegram:12345"
messages: list[dict] = field(default_factory=list)
def add_message(self, role: str, content: str):
"""Add a message to the memory list."""
self.messages.append({
"role": role,
"content": content,
"timestamp": datetime.now().isoformat()
})
Explanation:
messages: A simple list in Python memory holding the conversation.add_message: A helper to format the data (User or Assistant) and timestamp it.
The SessionManager handles the files. It ensures we don't lose data if the bot crashes.
# nanobot/session/manager.py
def save(self, session: Session) -> None:
# 1. Calculate the file path based on the session key
path = self._get_session_path(session.key)
# 2. Open the file in Write mode
with open(path, "w") as f:
# 3. Write metadata (creation time, etc.)
f.write(json.dumps(session.metadata) + "\n")
# 4. Write every message as a new line
for msg in session.messages:
f.write(json.dumps(msg) + "\n")
Explanation:
The Session History is great for chatting, but it gets too long. If you chat for a month, the file becomes huge, and sending all that text to the LLM is expensive and slow.
We need a place for Consolidated Facts.
This is handled in nanobot/agent/memory.py.
We use Markdown files because they are easy for both humans and AI to read.
MEMORY.md: The core brain. Contains facts like:> User's name is Alice. She likes Python. We discussed the weather on Tuesday.
HISTORY.md: A raw log of interactions, useful for searching later.# nanobot/agent/memory.py
class MemoryStore:
def __init__(self, workspace: Path):
# Define where our brain files live
self.memory_dir = ensure_dir(workspace / "memory")
self.memory_file = self.memory_dir / "MEMORY.md"
def read_long_term(self) -> str:
"""Read the persistent facts."""
if self.memory_file.exists():
return self.memory_file.read_text(encoding="utf-8")
return ""
Explanation:
MEMORY.md as "Background Knowledge."Now we have two memory sources. How does the Agent Loop use them?
We combine them into the Context. When we send a request to the LLM Provider, we construct a "prompt sandwich."
In the code, we merge these strings before sending them to the provider.
# Inside your ContextBuilder (conceptual)
def build_context(self, session, memory_store):
# 1. Get the deep memories
long_term = memory_store.read_long_term()
# 2. Get the recent chat
recent_chat = session.get_history(max_messages=20)
# 3. Combine them
messages = [
{"role": "system", "content": f"Here is what you know:\n{long_term}"},
*recent_chat
]
return messages
Now, even if the recent_chat is empty (because you just restarted the bot), the long_term variable still contains: "User is named Alice."
The bot will instantly "remember" Alice.
When a message arrives from the Channel Gateway, the first thing the system does is load the session.
Here is the logic inside SessionManager.get_or_create:
# nanobot/session/manager.py
def get_or_create(self, key: str) -> Session:
# 1. Check if we already have this session in RAM (Cache)
if key in self._cache:
return self._cache[key]
# 2. Try to load it from the .jsonl file on disk
session = self._load(key)
# 3. If file doesn't exist, create a brand new empty session
if session is None:
session = Session(key=key)
# 4. Save to RAM for next time
self._cache[key] = session
return session
Explanation:
self._cache) so the bot responds instantly.In this chapter, we gave our bot a memory span longer than a goldfish.
Now our bot can remember us! But currently, it can only talk. It cannot do anything. It can't check the weather, search the web, or run code.
To fix that, we need to give the bot "hands."
In the next chapter, we will build the Tooling System.
Generated by Code IQ