Why would I tell a chatbot how I talk to 250 people? I built a cat that learns it
I write to three kinds of people every week, and I sound like a different person to each one. My manager gets updates on tasks and deliverables. I always respect them by saying their name first: Priya, these tasks are done. My clients get messages that are very to the point: a requirement workshop,

I write to three kinds of people every week, and I sound like a different person to each one.
My manager gets updates on tasks and deliverables. I always respect them by saying their name first:
My clients get messages that are very to the point: a requirement workshop, a feature I'm explaining, a pain point they brought to the project. User adoption is the main key for them. I make sure the empathy is there and that we deliver what we promised.
My college friends get no restriction at all:
Same me, three personas. And every time I want an AI to help with any of them, I have to leave the screen I'm on.
Purrsona maps how I write to each person, platform and topic, and shows which edits taught it. It lives on a key: plug it in and the cat writes like me; pull it out and the laptop forgets me. Built for Graph Hacks: Context for AI Agents (WeMakeDevs × FalkorDB), Track 02.
Option+M in a reply box: the cat checks my voice graph and writes like me.
I'm on LinkedIn, so I navigate out of the window, open a chatbot and paste the link of the post. On Reddit I take a screenshot, because otherwise the chatbot says it doesn't have access to this post.
Then I say "make it sound like me", and it doesn't, unless I've set up its memory and preferences. So I explain how I sound, every time. A lot of tries later, it understands. Then the session is gone, and the context with it. It's very frustrating, for each and every post.
With Purrsona I don't set up anything for how I talk to this person, and this person, and this person. The graph and the cat learn it from what I actually send.
Same post, same shortcut. Top: key in, Friends rules. Bottom: key out, a generic reply.
I learned graphs two weeks before this hackathon, with my favorite game. Chatbots gave me generic Clash Royale advice and didn't know the cards I had. So I built ClashBuddy, a graph of cards, synergies, elixir and legendary status, and asked it how to improve my own game.
A writing voice is the same kind of problem. Give a chatbot my old messages and, at best, it finds ones that look similar. It doesn't see the full view: who each person is to me, which circle they're in, which platform I'm on, and which rule wins when two clash. A graph keeps those as connections, and FalkorDB walks them in milliseconds.
Every rule hangs off a scope and points back at the edit that taught it.
Say I'm at the airport, flying to another country. I'm not carrying my MacBook; I already have one there. Why should I sign up for a chatbot again and set everything up again?
You carry what it has learned with you. You're carrying your cat with you. I treat it as a pet: I take it everywhere I go, and it remembers who I am and how I talk with people.
That laptop needs the extension and the bridge once, but no account and no re-teaching: I plug the key in, press its button, and my voice graph is rebuilt from what the key carries.
Purrsona felt real to me when I talked with Mustafa on LinkedIn, then on Gmail, then on Reddit. The cat doesn't think Mustafa is the same everywhere. It drafts from who Mustafa is, what my relation to him is, and how I talk with him on this particular platform. That was the icebreaker for me. And all those chatbot tabs stay closed. I'm just there.
Under the hood, the graph gathers rules from five scopes: the person, their circle, the topic, the platform, and me. The narrowest rule of each kind wins. Kabir isn't in the graph at all, so every lookup is an OPTIONAL MATCH; a plain MATCH would find no Kabir and return nothing. Every read goes through FalkorDB's read-only ro_query. Claude only gets the post and the handful of rules that won. It never sees the graph and never writes a query.
// simplified from RULES_FOR_CONTEXT in api/purrsona/queries.py UNWIND scopes AS s // Priya, Work, the topic, LinkedIn, me MATCH ( s ) - [:HAS_RULE ] -> ( r: StyleRule ) WHERE r.superseded_at IS NULL WITH r, CASE labels ( s )[ 0 ] WHEN 'Person' THEN 1 WHEN 'Circle' THEN 2 WHEN 'Topic' THEN 3 WHEN 'Platform' THEN 4 ELSE 5 END AS rank ORDER BY rank WITH r.kind AS kind, collect ( r.text ) AS candidates RETURN kind, candidates [ 0 ] AS winner // one winner per kind Enter fullscreen mode Exit fullscreen mode With a fresh key, the cat asks a lot of questions. That's right. I stay on the same post and answer, in my own words, who this person is to me.
With a chatbot, when a session gets compacted or the conversation gets too large, it forgets who is who. And it's a very tedious task to tell it that I talk with these 250 people, and here are the rules for each. Nobody has the time. An AI should learn that by itself, on the same screen.
A very casual example: at first I talk with my girlfriend in a very romantic way. Now we're married, and we talk about our kids and the electricity bill. Why would I tell an AI that I'm married now? It should learn that from how I talk to my wife.
So when I send, the Learner compares what the cat drafted with what I actually sent, and saves the difference as a rule, linked to the edit that taught it. On Friday I changed a Kabir draft to open with "Bhaila", pulled the key out, plugged it back in, and the next Kabir draft started with "Bhaila".
// SAVE_LEARNED_RULE: the rule, its owner, and the edit that taught it MATCH ( p: Person { id: $person_id }), ( e: Edit { id: $edit_id }) CREATE ( p ) - [:HAS_RULE ] -> ( r: StyleRule { id: $rule_id, kind: $kind, text: $text, created_at: $at }) - [:LEARNED_FROM ] -> ( e ) // LINK_OVERRIDES: it outranks the broader rule, which stays as history MATCH ( r: StyleRule { id: $rule_id }) UNWIND $old_ids AS oid MATCH ( old: StyleRule { id: oid }) CREATE ( r ) - [:OVERRIDES ] -> ( old ) Enter fullscreen mode Exit fullscreen mode 4. Why: every draft explains itself I want to know why a draft sounds like me. Why does a draft to Mustafa say "bro"?
If, three months from now, I start calling him "broski", I don't tell a chatbot. I just send it that way, the cat learns it, and I can click "why?" to see how the reply was drafted.
I wrote the query behind it myself, step by step in FalkorDB Browser:
// WHY_THIS_DRAFT MATCH ( d: Draft { id: $draft }) - [:USED ] -> ( r: StyleRule ) <- [:HAS_RULE ] - ( s ) OPTIONAL MATCH ( r ) - [:LEARNED_FROM ] -> ( ev ) RETURN r.text AS rule, labels ( s )[ 0 ] AS scope, coalesce ( s.name, s.id ) AS owner, count ( ev ) AS evidence ORDER BY CASE scope WHEN 'Person' THEN 1 WHEN 'Circle' THEN 2 WHEN 'Topic' THEN 3 WHEN 'Platform' THEN 4 WHEN 'User' THEN 5 ELSE 6 END, evidence DESC Enter fullscreen mode Exit fullscreen mode Behind every reply there are three agents. The Drafter writes. The Critic's main job is to make sure the Drafter follows the rules the Learner learned; if a draft broke one, it sends it back once.
The Learner learns when I send. They never call each other: every step lands in the graph as a Step node, and every draft shows what the graph ran, each query with its milliseconds and rows. It feels like three people are backing me up.
This is the true portability. And if I want the AI to forget me, I just pull the key out. That's it. The bridge runs GRAPH.DELETE on my graph, and the next draft is generic.
Mustafa built the key, and I built the rest. In his clip, the key shows "Memory connected" with my graph and 35 rules, he pulls the cable, and the laptop says "Forgotten". Plug it back in, press the button, and everything comes back.
The real key on Mustafa's desk: plugged in, then the cable pulled.
A lot of things broke, and every round of testing on the real sites found something new.
Every site has its own text box. On X, Backspace stopped working after the cat filled the box. On Slack, a script's paste made the box empty itself. So every site gets a paste except Slack, which gets typed, and each site has a small adapter, so a new site is one more adapter.
Words inside words. "bhaila" matched my AI topic, because "ai" is inside it. My rule "Never says 'Bro'" flagged "broke". Both now match whole words, and I wrote the second fix myself.
A Critic that could never be satisfied. A two-sentence reply to Rohan counted as five, because "Hi Rohan,", "Regards," and "Maulik" looked like sentences, and the Clients rules require them. Now it counts only the message body.
The key and the laptop talking. Opening the USB port can reset the ESP32, so the bridge waits two seconds for the boot messages before it speaks. Every unlock is signed, after a button press. On the first real unplug, the graph was deleted but the laptop didn't say "Forgotten", until Mustafa fixed how a lost USB session is reported.
That's 1,424 draft lookups a second, and none of them saw another user's rules.
Not done yet: the key's flash and USB messages are plaintext (on purpose, so the board stays reusable), the Critic doesn't judge tone, and it doesn't learn from my own new posts yet.
Try the live demo: your own key, your own graph, and a practice page with the cat built in, nothing to install. For LinkedIn, X or Gmail, load the extension from the latest release. The README has a 10-minute local setup.
No API key? Paste these two queries into FalkorDB Browser and watch the narrowest rule win:
CREATE ( me: User { name: 'Me' }), ( work: Circle { name: 'Work' }), ( priya: Person { name: 'Priya' }), ( neha: Person { name: 'Neha' }), ( me ) - [:KNOWS ] -> ( priya ), ( me ) - [:KNOWS ] -> ( neha ), ( priya ) - [:IN_CIRCLE ] -> ( work ), ( neha ) - [:IN_CIRCLE ] -> ( work ), ( work ) - [:HAS_RULE ] -> (:StyleRule { kind: 'emoji', text: 'No emojis' }), ( priya ) - [:HAS_RULE ] -> (:StyleRule { kind: 'emoji', text: 'One emoji is fine for wins' }) Enter fullscreen mode Exit fullscreen mode MATCH ( p: Person ) - [:IN_CIRCLE ] -> ( c: Circle ) UNWIND [[ p, 1 ], [ c, 2 ]] AS scope WITH p, scope [ 0 ] AS owner, scope [ 1 ] AS rank MATCH ( owner ) - [:HAS_RULE ] -> ( r: StyleRule ) WITH p, r, rank ORDER BY rank RETURN p.name AS person, r.kind AS kind, collect ( r.text )[ 0 ] AS wins Enter fullscreen mode Exit fullscreen mode Maulik176 / purrsona Your writing voice on a key you own: a FalkorDB voice graph that writes like you, shows why, and forgets when you unplug. When I open the dashboard, I see my graph. These are my nodes, these are my neurons, this is where my blood is flowing.
I'm very sure this is a pain most developers, and most people, are facing. So try Purrsona and tell me: did you go to a chatbot less? Type less? Can you relate, or is there a tool you already use that solves it? Tell me what's missing. I'd like to build it.
Plug in anywhere. Unplug, and the AI forgets.