Originally published at getmental.app.
Mental is a mood journal for iPhone. It uses a language model in a few places, and four of them feed each other: tags, Memory, the evening wrap-up and Ask Mental. That is what makes them useful, and it is also the risk: once one model’s guess becomes the next model’s input, it starts to look like a fact. This is how the parts fit together in the current version, and the rule that keeps a guess from being recorded as true.
How the four parts connect
Every check-in has the same three pieces: a feeling picked on a seven-by-seven grid of energy and pleasantness, an optional note, and optional tags. Everything below starts from that.
| Part | What the model does | What code decides |
|---|---|---|
| Tags | Proposes up to five tags for one note | Which proposals land on the entry and which wait for you |
| Memory | Proposes claims about you, each with a quote from the note | Whether the quote is really there and the claim is worded safely |
| Wrap-up | Writes a short account of the day | What evidence it gets, and how each piece is labelled |
| Ask Mental | Talks one situation through and may point to a memory or a check-in | Which sources it sees, which links survive, and which sentences reach the screen |
Each part’s output is another part’s input. Tags go into the Memory prompt and into the wrap-up, which also gets how often each tag appeared on each of the previous seven days. Memories appear as stones on the home screen, in the wrap-up, in Ask Mental, and in the comparison step for every later note. What you write in Ask Mental can become memories in turn.
That chain is where a journal with AI can go wrong. If the wrap-up repeats a memory and the memory was a misreading, the misreading now looks confirmed. So each part follows the rule written on the type the Memory model returns: producers “emit operations and code decides whether to honour them.”
Tags: matched in code, new words wait for you
The tag prompt asks for what influenced the mood: activities, topics, kinds of people, places. “Deadline” is a good tag. “Anxious” is not, because the check-in already records the feeling. The prompt also rules out clinical conditions, diagnoses, and self-harm or substance labels.
The model sees your tag list, ordered by how recently and how often you use each tag. What comes back is sorted in code, not in the prompt. A word that matches one of your tags, ignoring case, hyphens and spacing, lands on the entry with your spelling. Anything else becomes a suggestion and stays off the entry until you approve it. A suggestion expires seven days after the model last proposed it, and a word you dismiss is never queued again. Matched and suggested tags share one budget of five per note, so approving every suggestion cannot overfill an entry. A tag you remove from an entry is never put back, whether by a later extraction or by approving a suggestion.
Memory: two calls, and the second never sees your note
Memory keeps track of people, situations and recurring themes from your notes, so you have less to hold in your head. It began as a single model call that read the note, compared it with what was already known and returned changes. That failed in a specific way. One note said “working with my gf from the coffee place we have near home.” With an existing memory, “You have a girlfriend,” in the same prompt, the model wrote “Your girlfriend works at the cafe.” The note never says that. The model had borrowed detail from the context it was only supposed to compare against.
Extraction is now two calls, the same split mem0 arrived at for the same reason.
The first call sees one note and its tags, and nothing else about you. It returns candidate claims. Each one has a kind (a fact, or a pattern you described), a subject, and evidence: a span copied from the note. An empty list is the expected answer for most check-ins.
The second call never sees the note. It gets the candidates and, for each one, the five nearest existing memories, dated by when each was first and last mentioned. It decides whether a candidate is new, repeats something known, refines it, replaces it, or duplicates another candidate.
What code checks before anything is saved
- The evidence has to appear in the note. The comparison ignores case and spacing and folds the curly apostrophes and dashes the iPhone keyboard inserts, because a model copies “can’t” back with a straight apostrophe. Before that fold, ordinary notes failed the check, and the retry failed the same way.
- A claim that one thing caused another is dropped unless it is written as your own account (“You said…”) and the quoted clause uses the same connecting word, with the claim’s words on the same side of it.
- A pattern needs the note itself to say the thing repeats, with words like “always”, “every morning” or “tend to”. One experience is not a pattern.
- A claim that talks about you in the third person is dropped. The code records this as a known cost: a phrasing slip can lose real content.
- At most five changes are kept per note, counted after the checks, so a rejected claim never takes a slot from a valid one.
- Subjects such as “work” or “sleep” are assigned in code against the existing ones, so “work” and “my job” do not become two threads.
- A reply in the wrong shape gets one repair request. In the comparison step, a single invalid decision sends the whole reply back, and if the repair fails as well, nothing from that run is saved.
/// True when `evidence` is a verbatim (modulo case/whitespace) substring
/// of `note`. This is the grounding gate: it permits inference — the
/// evidence licenses the claim, it does not have to restate it — while
/// blocking a claim the note cannot license at all, because nothing that
/// resembles it appears anywhere in the note.
static func isGrounded(_ evidence: String?, in note: String) -> Bool {
guard let evidence else { return false }
let key = evidenceNormalizationKey(evidence)
guard !key.isEmpty else { return false }
return evidenceNormalizationKey(note).contains(key)
}
The grounding check, from Mental’s source (MemoryOp.swift).
The comparison step picks those nearest memories by recency, importance and relevance with equal weight, following the retrieval function from the generative agents paper. Similarity alone would surface whatever is worded closest rather than whatever matters.
Your correction outranks the model
On the home screen each memory is a stone. You can open the check-in it came from, edit it in place, or ask Mental to forget it.
An edit is stored beside the model’s wording, not over it. Your text is what every screen shows and what every later prompt receives, and the model’s later updates change only its own wording underneath.
Forget writes a tombstone: the normalized text of the claim and a sentence embedding of it. Every later proposal, whether it is new, an update or a replacement, is compared against both. An exact match, or a similarity of 0.82 or more, is dropped. The threshold is generous on purpose. The code’s reasoning is that showing you something you rejected is far worse than silently dropping a near-duplicate you never see.
The wrap-up writes from evidence code assembles
The evening wrap-up is a short account of your day. The prompt asks for three or four sentences, and one or two on a quiet day. Everything above meets here, and it is the part where the model has the most room, so most of the work goes into what it is given.
Code assembles the evidence. Today’s check-ins go in whole: the time, the feeling’s name and its position on the grid, the tags, and your note, marked as “Source words”. The previous seven days go in only as figures computed per day, with no note text: how many check-ins, the average, lowest and highest position, the first and last time, and how often each tag appeared. A day without check-ins counts as unknown, not as neutral. Code also compares today with that week and says in the same line that this is a partial-day comparison, “Not a statement of normality.”
If Memory is on, up to three memories are added, chosen by how closely they match today’s notes and tags. Each one arrives as dated background with its kind: a fact, or an “uncertain hypothesis” for a pattern you have not confirmed. A memory recorded or edited after the day being wrapped up is left out, so a late wrap-up cannot rewrite what a day meant with something learned later. So is a memory whose source date code cannot establish.
The rules for the writing itself live in the prompt, which makes them instructions rather than guarantees. Among them: “Dated memories are background, never proof an event occurred today”; plans stay plans; a cause is kept only when you stated it in that day’s words; no advice or praise; and “Never aggregate hard days or link today’s low to a past low.”
An earlier version kept the model on a shorter leash. Code picked up to three “beats” from a fixed list and the model only phrased them, and a day with nothing worth saying got a pre-written line with no model call at all. The current wrap-up gives the model the whole day and relies on labels and prompt rules instead. That makes it the one place in this article where the line between what code decides and what the model writes is drawn by a prompt.
If you separately allow Health context, coarse ranges for sleep, steps, daylight and workouts are added. The app checks that permission again right before the request, and a pending job is tied to the permission that allowed it, so changing it makes an older pending result unusable. The finished wrap-up is kept with your journal.
Ask Mental: your words can become memories, its replies cannot
Ask Mental is for one situation at a time: a decision, a disagreement, something you need to say. It answers from what you write, and when a memory or a past check-in bears on the situation, it can use that too. It took Mirror’s place in the app; past Mirror chapters stay readable in the Mirror archive, under You → Privacy & data. This is where the loop could start feeding on itself. If Mental’s advice were remembered as something you said, a later answer would quote it back to you as a fact about you.
Code chooses the context. It matches the words of your latest message against your memories, noted check-ins and suggested actions, allowing for inflections such as “sister” and “sisters”, and sends at most eight. Earlier replies from Mental play no part in that match. The comment on the function puts it this way: “Previous generated advice cannot increase a source’s relevance or resurrect an obsolete subject.” Each memory arrives with a label: one you corrected, a saved memory, or an “Unconfirmed possible pattern, not established fact.” A memory whose source check-in was edited after it was written is left out, unless you corrected the memory yourself. A suggested action you completed arrives only as a status; whether it helped is included only if you said so in a later check-in. If Memory is turned off, Ask Mental sees none of these sources at all.
Replies are plain text that appears as it is written, one sentence at a time. Code holds each sentence back until it ends, then checks everything shown so far plus that sentence for a link, a phone number, a hotline or a short code that you did not write yourself. A sentence that fails is never shown. When the model mentions a detail from a source, it marks the phrase with the source’s ID. Code keeps at most two marks, and only for IDs it supplied; any other mark goes back to plain words. Markdown is stripped. The relay and the app run the same gate, and the finished reply is checked once more, whole, before it is saved. Help resources come from a fixed footer that the app draws at the end of every conversation.
When a conversation reaches a conclusion worth keeping, Mental offers a keepsake: a short takeaway and, when it fits, words you could say. A separate call decides that, and it can answer only one of three ways: nothing yet, ready, or acute danger. The keepsake goes through the same checks, and one that contains a link or contact details is not offered. Once the answer has been acute danger, that conversation offers no keepsake at all.
Each reply records which sources it used, including the ones it inherited from earlier replies. If you later delete or correct one of them, every reply that depended on it is left out of what the model sees next. Before a new reply is shown, the app also checks that none of its sources changed while the model was writing.
The prompt tells the model that “Previous Mental advice is not evidence about the user.” Code holds the same line in two places.
If Memory is on, your messages in Ask Mental feed it the way a check-in note does. Extraction sees the question you were answering, but only as context, and a claim that names something only the question said is dropped:
/// True when a memory claim names something only Mental's question said:
/// a content word of the claim (at least 4 characters, not a stopword)
/// stem-matches a word of the question but no word of the person's message
/// or of the claim's evidence. "Does she still host?" answered "Yes, for
/// five years." cannot become "You've hosted for five years". Deterministic
/// by design: some legitimate short answers yield no memory.
static func borrowsFromQuestion(claim: String, question: String, message: String, evidence: String?) -> Bool {
let asked = words(question).map { Array($0) }
guard !asked.isEmpty else { return false }
let said = words(message + "\n" + (evidence ?? "")).map { Array($0) }
return words(claim).contains { word in
guard word.count >= 4, !stopwords.contains(word) else { return false }
let token = Array(word)
return asked.contains { matches(token, $0) } && !said.contains { matches(token, $0) }
}
}
The question check, from Mental’s source (AskMentalModels.swift).
A conversation can also become an optional check-in. The draft note is written only from your messages, never from Mental’s replies or your memories, and it has to come with one to four supporting excerpts that code finds word for word in your messages. You pick the mood, edit the note and decide whether to save it.
Mental’s replies themselves never enter Memory. That is what stops the loop from closing on its own output.
What leaves the phone
The app holds no key for the AI provider. Each request is signed with Apple’s App Attest and sent to Mental’s relay on Cloudflare, which forwards it to DeepInfra. The model is currently GLM-5.2. The prompts for tags, Memory, the wrap-up and Ask Mental are kept on the relay rather than in the app, and the relay only routes to the provider the app’s consent screen names.
A request carries what its feature needs. For a check-in that is the note, its tags, the date and the position on the grid; for the wrap-up, the evidence described above; for Ask Mental, the recent messages of one conversation and up to eight memories, check-ins or suggested actions. It carries no name or account identifier. Health data appears only in wrap-ups, only as coarse ranges, and only after its separate opt-in. For tags, Memory and the wrap-up, which run as jobs, the relay keeps the encrypted request, the job’s status and its result for up to 24 hours. The privacy policy describes the relay and the provider.
In the You tab, AI has one switch for everything, one per feature, and a separate one for Health context. A note saved while a feature was off is not processed later just because you turn the feature on. As the code puts it, “Missing provenance never becomes permission merely because today's AI is on.” You can include an older note yourself. Each check-in shows what ran on it, such as “Finding memories…” or “No new memories.”, with a retry button when something failed. Tags, Memory, wrap-ups and Ask Mental are part of Mental Pro, and every AI feature stays off until you turn it on.
iOS suspends apps mid-request, and a model call can take most of a minute. So every request for tags, Memory and the wrap-up is submitted as a job, and the result is collected the next time the app runs. Resubmitting a job does not run it twice, and every attempt is signed again, because App Attest rejects a reused signature.
Where this falls short
- Grounding proves the quote exists, not that the conclusion drawn from it is right. A claim can quote your note exactly and still misread it. The code says so beside the check: “Correctability remains the real safety net.” It only works if you notice.
- Forget hides a memory; it does not erase it. The memory stays in your journal’s storage marked as rejected until its source check-in is edited or deleted, and the tombstone keeps a fingerprint of the claim so it cannot come back. Clearing your history deletes both.
- Editing or deleting a check-in deletes every memory drawn from it, including memories you edited and memories other check-ins also support. The code takes the cautious side on purpose, because it “cannot prove that the remaining sources support unchanged combined wording or a correction.” The cost is that you can lose a memory you wanted to keep.
- A later note can lead the model to propose that a new claim replaces an old one, and that can retire a memory you had edited. The replacement still has to pass every check above.
- Memories are written in English whatever language you write in, and the checks for causes and repetition match English wording only.
- The wrap-up’s writing rules are prompt instructions. Code controls what the model sees, not every sentence it writes.
- Ask Mental’s advice is a model’s advice. Code controls what it can see and cite, not whether the advice is good.
None of this makes the model reliable. What it does is narrow what a mistake can do: a wrong tag waits for your approval, a wrong memory can be corrected or forgotten and does not come back, the wrap-up sees labelled evidence instead of guesses presented as facts, and nothing Mental says in Ask Mental is remembered as something you said.
Sources and context
Code excerpts and quoted comments are from Mental’s iOS app and its relay, in Mental 2.3, the release that introduced Ask Mental. The mem0 paper describes the extract-then-update design; Generative Agents describes the recency, importance and relevance retrieval score. Mental is a journal, not a medical device, and nothing here is a claim about health outcomes.
More on getmental.app:
- Memory in Mental: what Memory keeps and how you correct it.
- A private mood tracker: what stays on your iPhone and in your iCloud.
- Privacy policy: every data flow, including the AI relay.
Mental is on the App Store.
Top comments (0)