I'm Sam, I work on heyaifriend, a free AI friend that lives in Telegram. This is a write-up of how its memory works, because "it remembers you" is easy to say and easy to build badly.
The short version: the bot keeps a small note about you, not a transcript of everything you ever said. The long version follows.
The constraint that shaped everything
We wanted three things at once. The friend should remember your week, we should keep as little raw conversation as possible, and the user should be able to look at, clear or delete whatever is kept. A single long-lived conversation per user fails the second and third goals: history grows forever, and "forget" becomes a lie because the model still has it all in context.
So we did not build that.
One session per user, started per message
The gateway is a small Node service that talks to Telegram over long polling. When a message arrives, it does not talk to a long-running agent. It composes a system prompt, starts a short-lived process that answers once, sends the reply and exits. Each user has their own session folder on disk, so the process continues that user's conversation and no one else's.
The system prompt is assembled fresh from parts: a base prompt with the hard rules, a mode block, a persona, a style note and the user's memory note. The process that answers has no tools at all. It cannot browse, run code or read files; it only produces text. That removes a whole class of "what if the model does something odd" worries.
Per-user serialization matters more than it sounds. Each chat has a mutex, so two quick messages never run two turns at once, and a global limit caps how many chats are being answered in parallel.
The memory note
Inside a single session the model has the recent conversation. Across days, that is not enough, and we do not want to keep conversations forever. So there are two small text files per user:
- a memory note: names, people, plans, things that stressed you out
- a style note: how you like to be talked to (short, jokey, no emoji)
Both are injected into the system prompt on every turn. They are short on purpose. A note of a few hundred words is cheap to send and easy for a person to read.
The memory pass
Every six hours, a scheduled job goes through the chats where the user wrote since the last run and has been quiet for at least twenty minutes, so it never rewrites anything mid-conversation. For each one it takes the last 40 messages from the session, asks the model to rewrite the memory note and the style note, and then asks a second time for a neutral operational summary that contains no chat content, which is what we look at when we need to know whether the system is healthy.
Two details helped:
- The pass rewrites rather than appends. Appending leads to a note that grows until it is just the transcript again. Rewriting forces a decision about what still matters.
- If the model output does not parse, nothing is written. A bad night leaves yesterday's note intact instead of replacing it with garbage.
The result is that a few days later the friend can ask how your interview went, even though the raw messages from that day may be long gone.
What gets deleted and when
Conversation history is deleted 30 days after your last message. A sweep runs at startup and every six hours. It removes the session files and the composed prompt for any chat that has been quiet longer than the window, and resets the session so the next message starts a new one built from your memory note. Messages the bot sent do not extend the window, only yours do.
The memory and style notes are kept after that, because they are the whole point of remembering you. That is why they are the part you control directly:
-
/memoryshows you what the friend remembers -
/forgetclears the notes and also the session files. This part is easy to miss: if you only clear the note, the model still remembers everything from its history, so forgetting has to delete both -
/exportsends you a file with your memory and style notes -
/deleteremoves everything we hold for you: your chat folder, the metadata and the database rows
There is also /invite, which gives you a personal link to share with a friend. All we count from it is how many people arrived that way.
We never sell data and never use chats to train models. Those are policy, not features, but they sit next to the technical controls on purpose.
Texting like a person
Memory is half of "feels like a friend". The other half is pacing, and it is mostly boring arithmetic.
A raw model reply arrives as one big block. People do not text like that. So the gateway splits a reply at paragraph breaks and then at sentence ends when a paragraph runs long, aiming for one or two sentences per message and at most four messages per turn. Before each message it shows Telegram's typing indicator for roughly 600 ms plus 45 ms per character, with some random jitter, clamped between about one and seven seconds. The time the model already spent thinking is subtracted from the first message's delay, so it never feels slower than it has to.
If someone sends three messages in two seconds, the gateway waits for the pause and answers the joined text once, instead of firing three separate replies that talk over each other.
Rate limits that do not feel like a wall
A free service needs limits. Ours are a sliding window (20 messages in 10 minutes) and a daily cap per user, counted in the user's own time zone.
When someone hits a limit, the bot does not just refuse. It stores the message, replies once with a friendly "give me a moment" line in English, Dutch or German, and answers the saved message automatically when the window opens, with a little jitter so it does not feel like a timer. A message sent after the deadline merges with the saved ones and gets a single answer. Pending text lives only in the private database, never in logs, and /forget and /delete remove it too.
Check-ins, with brakes
The friend can also message first: a check-in a couple of days after you start, another about a week in, and occasional nudges. Every one of these is rule-bound: quiet hours in your time zone, a weekly maximum, backing off after unanswered messages, and a hard stop if you pause or block the bot. They do not count toward your daily cap, and a user who writes again resets the counters.
What I would tell someone building something similar
- Make forgetting delete the model's context too, not only your notes.
- Prefer rewriting a small note over appending to a log.
- Spawn per message. It costs a little startup time and buys you isolation and an honest delete.
- Treat pacing as part of the product. It is a few dozen lines and changes how the whole thing feels.
- Never let the herder, meaning us, read the raw chats. We work from metadata and neutral summaries.
If you want to see how it feels, it is free, no app and no signup: press Start and say hi.
Written with help from AI, checked by us.
Top comments (0)