A memory that does not repeat itself
A memory that keeps everything you say ends up keeping the same thing several times, because you never say it twice in the same words. XNeuronal was designed to recognise a memory that is already there and reinforce it instead of creating it again. On 22 May, we found out that this mechanism had never fired. The day repaired the recognition, added a second criterion for rewordings, and gave two gestures for tidying by hand: a cleanup sheet that actually cleans, and multiple selection in the memory list.
The same brother three times
Hannah has been using the app for a month. One evening in March: "My brother Owen lives in Bristol." Card created. Two weeks later, while planning a weekend: "I'm going to my brother's in Bristol on Saturday." Reminder set, and one more card about Owen. Then, one morning, while talking about something else: "Owen, that's my brother, he works in a lab." New card.
Three sentences, one person, one fact. In the memory, three separate memories, each with its own date, its own wording and, for the third, a detail the other two do not have. When Hannah asks "what do you know about Owen?", the answer is right, but it has a stammer to it: the same thing said three times, with small variations. And in her memory list, three lines that look alike, with no way of telling which one is the good one.
This is not a failure of understanding. The app understood each sentence perfectly well. It is a failure of recognition: it did not see that the third sentence was about the same thing as the first.
What the memory should have done
This case was planned for. From the start, every memory carries a fingerprint: a long sequence of numbers that sums up the meaning of the sentence, not its words. Two sentences that say the same thing in different words have close fingerprints, and closeness is measured by a score between 0 and 1.
The mechanism is simple on paper. When a new memory arrives, its fingerprint is computed and compared with those of the memories already stored, and if one of them is close enough, nothing is created: the existing memory is reinforced. Its mention counter goes up, its last-mentioned date is refreshed, and it is this counter that later decides what comes up first when the memory is read in layers. A fact repeated three times should be one strong fact, not three weak ones.
The day before, on 21 May, the assistant had reviewed its own memory at the request of the person using it, on the account used for testing. Its report left no room for doubt: groups of cards restating the same identity, a trip told from a dozen angles, and a contradiction never settled about the destination of that trip. The database confirmed it, and worse: not one memory on that account had ever been reinforced. Not a single one, since the very first sentence. The mechanism was not badly tuned. It had never fired.
The bug that made no noise
Three causes, stacked, and none of them produced an error.
The first lay in how the fingerprint comes back from the database. It is stored there as a sequence of 1,536 numbers. But the layer that hands it back to the server delivers it as text: the list of numbers written between square brackets, with commas. The code that compared fingerprints began with a common-sense check, "do both fingerprints have the same length?". And it compared the length of the text, some thirty thousand characters, with 1,536. Never equal. Every candidate was discarded before the comparison even happened, silently, as if it did not exist. Two strictly identical sentences, word for word, coexisted in memory for that reason alone.
The second cause would only have become visible once the first was lifted. The threshold above which two memories count as the same was set at 0.85. That is a good threshold for a sentence copied out almost verbatim. It is a bad threshold for a natural rewording. Measured on real memories, "my brother is called Owen" and "Owen is Hannah's brother" score around 0.72. Same content, different phrasing, flat rejection. And nobody says the same thing twice the same way.
The third cause was an absence. Computing the fingerprint sometimes fails: a remote service that coughs, a reply that never arrives. The code allowed for that case and stored the memory anyway, without a fingerprint, but without leaving the slightest trace in the logs. Part of the memories therefore had no chance of ever being recognised, and nobody could know.
Nothing crashed. The app answered, cards were created, the memory grew, just too fast and in the wrong way, and it took the assistant itself being surprised by it for anyone to go and look.
Two tiers for recognising a memory
Repairing the first cause takes a few lines: before any comparison, the fingerprint is re-read and converted into numbers, whether it arrives as a list or as text. The same fix was applied to search by meaning, the one that answers "what do you know about Owen?" and which suffered from the same defect without it showing: it was searching among zero candidates and falling back on other criteria.
The second cause called for a choice. Lowering the threshold to 0.70 for everyone would have caught the rewordings, but also false twins: two different facts that share a vocabulary, a person with two friends of the same first name. Keeping 0.85 let the duplicates multiply. The answer was to stop depending on a single number.
Above 0.85, the memory is recognised directly: it is a tight paraphrase or a near copy. Between 0.70 and 0.85, it is recognised only under two conditions: both memories are of the same type, and they share at least one person or one theme. Those people and themes are not guessed at comparison time: they are set on each memory when it is created, by the model that read the sentence. Two unrelated Owens will not share a theme. Two wordings of the same Owen will share at least his name.
Every reinforcement now leaves a trace: by which tier the memory was recognised, and with what score. The two thresholds are reasonable bets, not measured truths, and the only way to adjust them is to read back, after a few weeks, what the mechanism actually merged. As for the third cause, a failed fingerprint computation is now written to the logs: the memory is still stored, but we know it was born without one.
What was done with the duplicates already there
Repairing the mechanism does not erase what it let through. The existing duplicates stayed in memory, and the repaired code would never touch them: it only compares a new memory with what exists, not what exists with itself.
On the test account, the tidying was done by hand, and the way it was done says something about the product. No memory was deleted. The duplicates were muted: a status that removes them from everything the assistant reads and everything it displays, but leaves the row in place. For each group, one card was kept as the reference, the most complete one, and the others were silenced. The gesture can be reversed in a word.
The contradiction was handled differently. Two cards were fighting over the destination of a trip. One was wrong, the other right, and the person concerned settled it. The wrong one was not erased: it moved to the "erroneous" status, with a link to the one that corrects it. It is the same principle as a correction spoken in conversation: the old memory stays, marked as outdated, linked to the new one. You can still answer "why did you think it was Lisbon?".
A tidy-up done by a developer in the database is not a solution: what was missing were gestures, in the app, so that everyone could do the same thing to their own memory.
The cleanup sheet that cleaned nothing
There was already a place for that, in the settings: a sheet called "Clean up the past". You ticked categories, appointments, reminders, tasks, memos, you chose between archiving and deleting, and the app swept away what was behind you. That is what it was supposed to do. On 22 May, when it was tried in earnest, it answered "nothing to clean" almost every time.
It had three reasons to be wrong. The list it swept came from the calendar, which only shows active or done items; anything already archived once was invisible to it, and a second pass in "delete" mode found nothing left. A cautious filter, on the phone side, set aside any item whose end had not yet passed; but an all-day appointment ends at 11:59 pm, and a task done in the morning stayed "upcoming" until the evening. Finally, muted memories, the ones the assistant had already agreed to forget on request, belonged to none of the families the sheet knew how to see. You could say "forget that reminder" and never be able to truly erase it.
The day reworked the sheet from top to bottom. The sweep now sees every terminal state, archived and muted included, and it judges "past" what the calendar judges past: resolved, or window closed. The "archive" mode is gone. It was a trapdoor: nothing in the app let you see what had been archived, or bring it back. Only one path remains, explicit, preceded by a red confirmation that says what it does: this is permanent.
And because tidying is not always about the past, the sheet gained a question: how far? Either only what is past or resolved, the original behaviour, finally working. Or everything that belongs to the ticked categories, whatever the date or state, upcoming appointments and undated memos included. That is the radical option, for "wipe all my recipes", and it carries its own warning. The sweep is now done in a single operation on the server, rather than as a queue of small deletions. The sheet is called "Clean up the memory": "the past" was no longer telling the truth.
Choosing by hand: a long press, checkboxes, two buttons
The cleanup sheet works by category. Hannah, for her part, wants to remove two of the three cards about Owen, and nothing else. For that, a gesture was needed in the memory list, the one that shows each memory on a line.
Since 22 May, a long press on a line switches the list into selection mode. A checkbox appears in front of each memory, the header shows how many are ticked and offers to take them all or to exit, and a bar at the bottom of the screen offers two actions. Tapping a line ticks or unticks it, without opening the card, as long as you have not left the mode.
The two actions are not twins. "Mute" does what the hand tidy-up had done on the test account: the memory leaves reading and display, but the row still exists, and it can come back. "Forget for good", in red, erases permanently, after the same confirmation as the cleanup sheet. The app's general rule is to destroy nothing; this button is the acknowledged exception, when it is the person themselves asking for it, knowing what it does.
Hannah ticks two lines, presses "Mute", and her memory knows only one Owen. The next time she talks about him, the reconciliation will happen on its own.
What does not enter the memory stays within reach
A memory that does not repeat itself is also a memory that knows what it should not keep. A weather forecast, the opening hours of a duty pharmacy, the state of the traffic: these answers are displayed without becoming memories, in a page that closes and leaves nothing behind. That was the previous day's choice, with one small practical problem: once the page was closed, you had to ask the question again to see it.
Since 21 May, the assistant gives each of these pages a title, two or three words you recognise at a glance, "Bristol weather", "Duty pharmacy", and that title stays under its reply, in the conversation, as a small cyan chip. Tapping it reopens the page exactly as it was, from the phone's memory, without asking the server anything and without creating a card. The chip lives for the length of the conversation; when the conversation starts over, it disappears with it. The next day, the same gesture was extended to the planning, tasks and memos pages, views assembled on the fly with no card behind them either, and every line on those pages now opens with a tap.
That is the counterpart of choosing not to keep everything: what is not a memory does not clutter the memory, but it does not evaporate the second you leave it.
What remains open
The two thresholds, 0.85 and 0.70, are waiting for their first weeks of traces before being adjusted. If they merge too much, they will be tightened; if they let duplicates through, they will be loosened.
The duplicates born before 22 May, on accounts other than the test one, are still there. The repaired mechanism does not catch up with them; it only stops new ones from being created. A periodic pass that would spot pairs that are too close among what exists and merge them without anyone asking is the logical next step. It is not written. In the meantime, multiple selection does the job, line by line, and that is precisely why it arrived the same day.
Finally, the list's bulk actions still go through one request per memory. On ten lines, it does not show; on two hundred, the server will have to learn to handle them as a block, as the cleanup sheet already does.
For Hannah, the outcome fits in one sentence. She can talk about Owen as many times as she likes, in whatever words come to her, and her memory will keep only one, more and more sure of itself. And the day she wants to sort things out herself, it takes a long press and two taps.
