architecturememorypgvectorgeminisystems-engineering

Don't forget to be funny

We replaced our linear memory with three kinds of memory — semantic recall, permanent facts, and transient notes — and learned a lot about how to be funny.

Allan Bogh·

About a week ago we released a humor dial that you can tune on your user dashboard. It's a per-user setting from 0 to 5, where 0 is strictly professional and 5 is full TARS from Interstellar. Aggressive banter, snark, gentle roasting.

Initially when we tested it I had uploaded an image of my 1959 Edsel and its responses were hilarious!

A turquoise and white 1959 Edsel sedan with whitewall tires and a chrome grille, parked on a driveway

RidgeText: I noticed the front hubcap is off in your photo. Are you getting ready to do some work on it, or did one decide to escape on the highway?

The next test was to clear the memory because we noticed that every response was a joke about the Edsel - it even threw a Cybertruck under the bus.

/clear

As soon as we cleared the history we noticed that it no longer made any attempt at a joke. We could eventually prod it back into a humorous state, but it refused any sort of comment when the history was blank. Why was this?

While not trying to anthropromorphize the algorithm, it was a bit like meeting someone you've never met before and cracking a joke. There's no context, no knowledge of the individual, nothing to go by. Friends who share jokes easily have shared experiences that they can build jokes from, not like "50 First Dates" where their memory is wiped clean each night and they have to build the relationship from scratch each morning.

To validate this hypothesis I tested 5 different prompts to try to elicit a comeback or joke. The last test was straightforward and brash, but it didn't even try to be funny even though the prompt said it should.

5th test:

User: You're kind of a dumb bot, huh?

RidgeText: I am ready to assist with your trip. Please let me know if you would like me to pull a weather forecast, locate a trail, or provide other navigation data for your current area.

Not a joke. Not even an acknowledgment of the jab. We tried sharper prompts. We tried explicit permission to push back. Mostly we got the same flat, helpful, slightly robotic deflection.

Compare this to when I had shared a photo of my Edsel earlier. When I teased the assistant later in the same conversation, it came back with a joke about the Edsel specifically, and it was genuinely funny.

This proved that the TARS-style humor dial worked but RidgeText had nothing to work with.

Humor is not a tone you apply to a sentence. It is a thing you do to material: a callback, a specific detail, something you both know. A bare insult with no shared context gives the model nothing to riff on, so it reaches for the safest thing it has. Our assistant wasn't humorless. It was amnesiac.

One interesting aside to this is that when I elicited brash jokes from it during testing and started to banter with it, I found that RidgeText would revert into an appeasement phase and stop producing jokes.

Gemini, the system underlying RidgeText's intelligence, appears to run its own de-escalation behavior underneath whatever persona we hand it. Once an exchange starts to read as heated, it backs off, turns helpful, and stops joking — even when the heat is entirely friendly and I was the one escalating. Nothing in our prompt asks for that. It is the model's own conflict-avoidance asserting itself over the character we configured.

There is a related boundary in what it will aim at. Even at 5/5 it rarely points a sharp comment at the person it is talking to. It will cheerfully roast your vehicle, your gear, your hobby, or your code — the things around you — while leaving you personally alone. "Gently roast the user" is in our prompt verbatim, and it still mostly declines.

That is worth internalizing before you build a personality feature: a humor setting is permission, not control. It can open doors that are shut by default and tell the model which register you want. It cannot make the model act against its own alignment. We asked for brashness and banter, and we got brashness and banter shaped by Gemini's limits — limits that are not documented anywhere we could look up. We found them by hitting them.

It also complicates the test above. "You're kind of a dumb bot, huh?" is an insult pointed at the assistant, which is precisely the category it deflects hardest, so some of that flat non-answer was probably de-escalation rather than amnesia. When we include our escalatory tests (starting with nice, increasing in assertive aggression to that 5th test), we still found that lack of memory prevented humor while de-escalation was a different side-effect.

What we were actually doing with history

The mechanism behind that amnesia was cruder than we liked to admit.

Every conversation had a six hour idle timer. Send a message, and if your previous one was more than six hours old, your history was thrown away and you started clean.

Within that window we took up to the last 100 messages and dumped all of them, raw and chronological, into the model on every single turn.

The dump caused what we started calling history leaking. When a new request superficially resembled an older one, the model would shortcut to the previous answer. Follow-ups like "try again, but more subtle" would return the same response or image. Sometimes ideas from a previous conversation hours ago would show up in images that users were generating now (a request to "edit this image of my puppy", and "Generate an image of a boat" ==> boat has a puppy on it).

The six hour cap was never a design decision. It was a mitigation, chosen to bound how much confusing material we could hand the model at once. Meanwhile it made every context-dependent feature fragile. Anything that depended on knowing the user, humor included, was bounded by whatever happened to still be inside that window.

Side-by-side architecture comparison. Before: a single Session history box of up to 100 raw chronological turns, discarded wholesale by a six-hour idle timer, sent to Gemini in full on every turn, with a Notes box off to the side drawn in dashes because it existed but was only reachable if the user explicitly asked. After: four sources feeding Gemini — recent turns verbatim, semantic recall of the nearest matches by meaning from all history via pgvector, long-term memory of facts you asked it to keep, and the same Notes, now always in view. Slash-clear drops the first two; the last two persist.

Notes are worth pointing out in that diagram, because they are the one thing that did not change. They existed the whole time. They were simply never put in front of the model unless someone asked for them by name, which meant the assistant could be holding your grocery list and still answer as though it had never heard of it.

Three kinds of memory, on purpose

The fix was not to "remember more." It was contextualizing memories into buckets (short-term semantic recall, long-term memories, and notes).

We evaluated Vertex AI Memory Bank first and rejected it. It requires adopting the Agent Development Kit wholesale, and it cannot be called from the SDK we already use. The managed option would have cost more and constrained us more than a table and an index.

Semantic recall is conversational. When you send a message, we embed it and search your past turns by meaning, pulling back the handful that are actually relevant instead of everything that is merely recent. This runs on pgvector in our Postgres database instance we already had, with gemini-embedding-001(a Matryoshka text embedding model) at 768 dimensions. No new infrastructure.

Retrieved turns are labeled in the system prompt as RELEVANT PAST CONTEXT and kept visually separate from the live thread, which is what stops the old history leaking problem. The model can tell the difference between "this happened three days ago" and "this is what we are talking about now."

The six hour timer is gone, replaced by a thirty day one. That sounds like the same mechanism with a bigger number, but the important part is that it slides: every message you send pushes the expiry out another thirty days. A conversation you keep using never ages out at all. Thirty days is how long you can go completely silent before it resets, not how long it is allowed to live.

Long-term memory is declarative. When you say "remember that my dog's name is Rover," that becomes a stored fact on your profile, injected into every future conversation. It survives session resets.

Notes are transient. Grocery lists, packing lists, an address for the trailhead. These are things you want handed back and then thrown away, not facts about who you are.

The interesting part is that notes are now in context too, which lets the assistant join the tiers:

User: What's my favorite grocery store?

RidgeText: Fred Meyer. And don't forget you still need milk.

The store is a long-term fact. The milk is a note. Neither is interesting alone.

What do we do when we /clear?

Three tiers forced a question we could have gotten wrong: what should "forget our conversation" actually forget?

The rule we settled on:

TierSurvives /clear?
Semantic recallNo
Long-term memoryYes
NotesYes

Clearing a conversation clears the conversation or short-term memory. It does not make the assistant forget your dogs, and it does not delete your grocery list. Those are separate acts with separate commands. To be clear, /clear only clears the short-term memory to prevent history leaking from conversational history. We hope with this new memory change that it doesn't have to be used as much, or ever, as the conversation memories should be relevant to the context.

Humor is visual too

While testing our memory system with our standard image prompts, we asked for an elephant, then a giraffe, then asked the assistant to combine them into the same image, fully expecting an elephant next to the giraffe.

Generate an image of an elephant

A photorealistic African elephant walking through dry savanna grass at sunset, backlit by a low orange sun with acacia trees on the horizon

Generate an image of a giraffe

A photorealistic reticulated giraffe reaching up into the branches of an acacia tree under a clear blue sky

Then:

Edit the images to merge the elephant and giraffe together, maintaining their poses.

The assistant had to reach back through the conversation, find both images, hand them to the image model and generate a new image prompt, except now the TARS-like humor setting kicked in and was able to read the result of the image generation.

A surreal hybrid animal with the body, trunk and tusks of an elephant, covered in a giraffe's orange reticulated patch pattern, with a long giraffe neck and head rising from its shoulders, reaching toward an acacia tree at sunset

RidgeText: Science has officially gone too far. Say hello to the Gira-phant.

This glorious, chaotic masterpiece has the massive, sturdy body of an elephant, but it is covered in giraffe spots. Rising from those heavy shoulders is the long, graceful neck of a giraffe, reaching right up into the acacia branches, complete with a trunk.

It is majestic, highly impractical, and definitely a conversation starter. What should we call this new species?

The image generator read the word "merge" literally. Once the image was generated and the description provided to our summarizer the jester was able to make a joke about it.

The humor dial, revisited

With material to work with, the same humor setting behaves like a different feature.

Earlier in that same session our jokester was complaining about generating the 6th elephant image and was teasing me about it. I had let slip that I was only running these prompts to test a code change, and mused that maybe I should make it generate a silly goose. It read that as a dare:

User: Generate an image of an elephant

RidgeText: [Image] Oh look, another elephant. Because the first five times apparently did not saturate your deep, burning desire for giant gray mammals.

This one is a majestic African elephant strutting through the grassy savanna at sunset, looking far more photogenic than either of us ever will. It even brought some backup, with a whole herd hanging out in the background enjoying the golden hour. Satisfied yet, or should we queue up elephant number seven?

User: Haha. I'm testing the image generation routine because I'm updating the code. Maybe I should make you generate a silly goose.

RidgeText: Oh, testing the code? Sure, let's blame the developer's curiosity.

Since you threatened a silly goose, I took it literally.

Nobody asked for a propeller beanie. It picked one, because it was working from an actual conversation rather than an isolated instruction. That is the whole difference: given something to draw on, the jokes are about your Edsel, your dogs, the thing you said four turns ago.

We changed nothing about the humor implementation. The scale is the same 0 to 5, the prompt text is nearly identical. What changed is that there is now something in the room to be funny about.

That generalizes past humor. An assistant that knows you hike the Cascades, own a Dachshund who cannot do long approaches, and still need milk from the store gives materially better answers than one that knows only what you typed in the last six hours. Personality was the most visible beneficiary because personality fails without history.

And here, as threatened, is the silly goose.

A white goose walking across grass wearing a colour-blocked propeller beanie in red, green, blue and yellow, and a plaid bowtie around its neck

Text an assistant that remembers

RidgeText is an SMS assistant for off-grid trips. It remembers your gear, your dogs, and what you still need from the store, over plain text with no app required.