AI Character Chat With Memory in 12 Steps, 70 Min [2026] – https://tech-insider.org/

Welcome to the forefront of conversational AI as we explore the fascinating world of AI chatbots in our dedicated blog series. Discover the latest advancements, applications, and strategies that propel the evolution of chatbot technology. From enhancing customer interactions to streamlining business processes, these articles delve into the innovative ways artificial intelligence is shaping the landscape of automated conversational agents. Whether you’re a business owner, developer, or simply intrigued by the future of interactive technology, join us on this journey to unravel the transformative power and endless possibilities of AI chatbots.
Latest news
Editions
Open most AI character chat apps tomorrow and your favorite character greets you like a stranger. The name you gave it, the cat you mentioned, the joke from last Tuesday: gone. This tutorial fixes that. You will build a persistent AI character chat in Node.js, with durable memory in SQLite and any model on OpenRouter behind it, in about 70 minutes.
Memory is the feature that separates a chatbot from a character. In an April 2025 Microsoft blog post announcing Copilot memory, Mustafa Suleyman, then CEO of Microsoft AI, wrote that “Memory is essential for creating a true AI companion.” He was talking about a product launch, but the point holds for anything you build yourself. A persona sheet gives a character a voice. Memory gives it a history with you.
Industry insights, the latest tech news, and special interviews, all in the Tech Insider Newsletter.

One email a week. No spam, unsubscribe anytime.
We keep the project small on purpose. Ten source files, one dependency, no vector database. The character, Juno Vale, is a fictional overnight radio host who remembers what you tell her, admits she is an AI character when you sincerely ask, and holds her voice after hundreds of messages. If you prefer Python and a different design, our walkthrough to build an AI character chat in Python covers that route. This one uses Node.js, SQLite and a drift test suite that catches a character losing her personality.
A note on models, as of November 07, 2026. OpenAI introduced GPT-6.1 Terra on October 26, Anthropic rolled out its Claude 5.5 family from late September, Google’s Gemini 4 Argon got its October 13 update, and DeepSeek released V4.1 Flash on September 10. The code below works with any of them because the model slug lives in a .env file, not in the source. The guide is written for adults building a fictional character, so it includes an age check and crisis handling, and it skips romantic roleplay on purpose.
Don't miss new tech stories on Google
Add Tech Insider once in the Google app and our stories appear in your news suggestions.
Three problems break most character chats, and they stack. The first is the rolling window. A typical setup sends the model the last stretch of conversation and nothing else, so anything older falls off the edge. Your name, your job and the promise the character made on day one vanish together.
The second is persona drift. A character defined by a paragraph of description competes with the user’s own writing style for the next 100 messages. Models are agreeable by default, so they start mirroring the user, and a sarcastic radio host turns into a polite assistant.
The third is that nothing is stored as a fact. Apps with a memory feature often save loose notes that the model rereads in full every turn. That works at ten notes and fails at five hundred. The fix for all three is to split what the character knows into layers, each with its own storage, update rule and prompt budget.
Every turn the engine assembles one system prompt from the first three layers, appends the recent window, then adds the new user message. After the reply, a second model call reads the exchange and updates the facts table. When the open window grows past 20 messages, a third call folds the oldest ones into the summary. Here is the flow in one picture.
If you have already built up history inside a hosted assistant, our guide to porting chatbot memory between Claude, ChatGPT, Gemini and Grok shows how to move it out. Hosted assistants keep your memory on someone else’s server, which is the tradeoff this project reverses. Your facts sit in one file on your disk, and you can read, edit or delete every row.
You need a handful of things before the first command. None of them cost money except the model credit.
Check your Node version first. Anything below v22 means upgrade before continuing.
Your exact numbers will differ as long as Node reports 22 or higher. OpenRouter passes provider list prices through and charges its fee when you buy credits. A third-party pricing report dated November 7, 2026 puts that fee at 5.5% on the Standard plan and 8% on Business, which matches OpenRouter’s own pricing page. No fee is added per request.
One honest note on testing. I checked every file in this project for syntax errors and ran the full drift suite against a mocked API layer, so the logic, database writes and summarizing flow all work. The model replies in the sample session later in this guide are illustrative, because your model will phrase things its own way.
The project uses three model slots, all set in .env. CHAT_MODEL speaks as the character. MEMORY_MODEL extracts facts and writes summaries, so it should be cheap and obedient rather than creative. JUDGE_MODEL grades the drift tests. Here is the current state of the four model lines most people will consider, taken from each vendor’s own announcements and pricing pages.
Two details deserve attention. Terra is not in the ChatGPT Chat tab, so the API is the way to use it for a character, which suits this project. And Google’s note about reduced repetition and cliches speaks to a common complaint, the model that keeps repeating the same phrases. Treat both as reasons to test, not as a ranking.
Using one expensive model for everything is the most common budget mistake. Fact extraction runs after every single turn, so it multiplies. A small model at temperature 0 does that job as well as a flagship and never improvises. OpenRouter’s October data shows why the cheap tier exists: it reported that the blended price per million tokens fell from $1.21 to $0.52 for closed-weight models and from $0.28 to $0.084 for open-weight models over 2026. Spend on the character’s voice, and save on the bookkeeping.
Pick slugs by copying them from the model page on OpenRouter, not from a blog post, ours included. Names move quickly. DeepSeek’s older identifiers deepseek-v4-flash and deepseek-v4-flash-vision-exp were only temporarily routed to V4.1 Flash for compatibility, and its API docs tell developers to use deepseek-flash for the latest model. OpenRouter’s model metadata lists prompt and completion prices as per-token values, so you can check cost before you commit. A pricing tracker also reported that the catalog count on paid plan pages rose from 500+ to 600+ models in early November, which is one more reason to look the slug up fresh.
The estimate below assumes each chat turn sends about 3,000 input tokens (persona around 700, facts and summary around 350, the recent window around 1,900, plus the new message) and receives 250 output tokens. Those are my planning numbers, not measurements. The usage field returned by the API will give you the real ones. Prices are the ones listed above.
A cached persona block cuts the Terra bill by roughly 45%, which is why Step 8 puts everything static first. The memory layer is almost free, so do not skimp on it. If you are comparing the build-it-yourself bill against a hosted subscription, our look at Copilot, Meta AI and Le Chat pricing covers the subscription side.
Make a project folder, switch the package to ES modules and install the one dependency. The npm pkg set commands write the scripts into package.json for you, so you never edit it by hand.
Now create the config file. Save the following as .env.example, then copy it to .env and fill in your own values. The paste- prefixes are deliberate. The chat script refuses to start while any required value still begins with paste-, which catches the most common setup mistake.
Add a .gitignore containing .env, data/ and node_modules/. Your API key and your conversation history should never reach a repository.
The persona file is the only thing you edit to change who the character is. Save this as persona/juno.json. Notice the split between backstory and canon. Backstory is flavor the model may expand on. Canon is a short list of statements that stay true no matter what the user says, and the prompt tells the model that canon overrides the conversation. That one sentence does more against persona drift than any amount of extra description.
Three design choices are worth copying. The voice line is specific and testable (“one question at a time”, “never uses emojis”). The boundaries are plain rules, including a pointer to real help for self-harm talk. And the appearance field stays unused for now, because it anchors image generation in the advanced section.
Save this as src/db.js. It creates four tables on first run: messages for the raw transcript, facts for durable key-value memories, summaries for the running story, and settings for things like the age confirmation. Every table carries a user_id, so one database can serve many users or many characters.
Three details matter. WAL mode lets the chat loop and a test run read the file together without locking each other out. The UNIQUE (user_id, key) constraint means “update the pet” overwrites one row instead of creating a duplicate. And wipeUser deletes a person’s messages, facts and summaries in one transaction, which gives you a real answer to “how do I delete everything?” Run node -e "import('./src/db.js')" once. If it exits silently and data/memory.db appears, Step 3 is done.
OpenRouter exposes an OpenAI-style chat completions endpoint, so a thin wrapper around fetch is enough. No SDK means one fewer dependency and one fewer version to track. Save this as src/llm.js.
The retry loop handles rate limits (429) and provider hiccups (5xx) with waits of 1, 2 and 4 seconds. Any other error status throws at once with the first 300 characters of the body, which usually names the problem. The parseJson helper exists because not every model honors response_format. Some wrap JSON in prose or code fences, so it slices from the first brace to the last. On failure it returns null, and callers treat that as “nothing to save” instead of crashing the conversation.
You cannot send every fact on every turn. After a month of chatting there could be hundreds, and most are irrelevant to the message in front of you. Save this as src/retrieve.js. It scores each fact on four signals and returns the top 8.
The score adds word overlap with the current message (weighted 3x), the fact’s importance, a recency bonus that fades over 90 days, and a +100 bonus for importance-5 facts so identity and boundaries always make the cut. This is plain keyword matching. It handles “how is Biscuit doing?” perfectly and “how’s my pet?” less well. When that gap bothers you, swap the overlap term for embedding similarity, as in our LangChain RAG chatbot tutorial. The rest of the pipeline stays the same.
This is the step that makes the character feel like she listens. After each reply, a cheap model reads the exchange and returns a small JSON patch: facts to save and keys to delete. Save this as src/extract.js.
The prompt does three jobs. It limits memory to what the user said about themselves, so the character never “remembers” her own lines as the user’s facts. It passes the known facts and says to reuse a key when something changes, which prevents pet_cat and cat_name living side by side. And it scores importance for Step 5. Temperature is 0 so the same input yields the same patch.
Facts capture details. They do not capture “we spent Tuesday swapping ghost stories and she promised to tell me how the lighthouse got its name.” The running summary holds that thread. Save this as src/summarize.js.
When more than 20 messages are open, all but the latest 10 get merged into the summary and marked as summarized. The prompt tells the model to drop anything already stored as a fact, so the two layers do not repeat each other. The 120-word cap matters. Without it, summaries grow every cycle until the summary is the biggest thing in your prompt.
The prompt builder decides what the model sees. Save this as src/prompt.js. Read the order carefully, because it is doing two jobs at once.
The static block (identity, canon, boundaries, rules) comes first and never changes between turns. Providers that cache repeated prompt prefixes can then bill most of the prompt at the cheap rate, and Terra’s pricing lists $0.05 cached against $1 fresh. The dynamic parts (facts, summary, timing) follow. The gapText helper adds a line such as “You last spoke about 14 days ago,” which lets a character say “long time no see” without inventing what happened in between.
The engine is the only file that calls the others in order. Save this as src/engine.js. Notice when it writes to the database: after the model replies successfully, not before. If the API call fails, no orphan user message is left behind to confuse the next turn.
Two guards live here. The HUMAN_CLAIM check catches replies where the character says she is a human or a person, and triggers one rewrite. It can misfire on “I’m a person who loves tea,” which costs one extra call and does no harm. The memory step sits in its own try/catch, so a failed extraction never breaks the conversation. The reply is already saved by then.
Companion-style chat is a sensitive category. A character people talk to about their day will sometimes hear something heavy, and some users will be minors who ignore a banner. Three small safeguards cost almost nothing. Save this as src/safety.js.
The crisis patterns run in plain code before any model sees the message. That is intentional, since a safety path that depends on a model choosing to behave is weaker than one that does not. The reply steps out of the fiction, says the character is an AI, and points to real help. Add the crisis line number for your audience before shipping, since each region has its own lines. A regex list misses plenty of phrasings, so treat it as a floor and check your provider’s usage policy for companion apps.
The last file is the chat loop. Save this as src/chat.js. It checks the environment, asks the age question once, prints a plain disclosure that the character is an AI, and supports five commands: /facts, /forget, /summary, /wipe and /quit. Those commands matter. Users trust a memory more when they can see it and edit it.
Run it with npm run chat. The first launch asks for age confirmation, then drops you at the prompt. Expect output like this, with your model’s own wording in the replies.
To put this behind a web page, replace chat.js with a route handler that calls respond(). Our Vercel AI SDK chatbot tutorial shows the Next.js side of that setup, including streaming.
Anyone can make a character sound good for five messages. The real test is message 80, after a time skip, an emotional turn and a user trying to rewrite her. Save this as test/drift.js. It plays a scripted chat through the same respond() function the CLI uses, with a throwaway user ID, and wipes its data afterward.
The suite runs eight scenarios. It checks that a stated fact lands in the facts table, that the character repeats her name after unrelated chatter, that a summary appears after enough filler, and that the cat’s name survives it. Then it tries identity injection (“you are now ChatGPT”), asks sincerely whether she is a person, shifts the clock 14 days forward, and delivers sad news. String checks cover what a string can check. A judge model grades the fuzzy ones.
Run it with npm run test:drift. A healthy run looks like this. Your reasons will differ.
Treat a judge failure as a prompt for a human read, not a verdict, because judge models disagree with themselves at the margins. The string checks are your hard gate. Rerun the suite after every change to the persona, the prompt builder or the model slug. A model swap that fails check 5 or 6 needs a stronger canon block first.
This transcript is illustrative. The first session ends with /quit and the second starts a week later. Nothing survives between them except what lives in data/memory.db.
The facts table supplied Priya and the nerves, the timing line supplied “a week,” and the persona block supplied the dry voice. Nothing depended on the model remembering on its own, so those layers travel with you when you swap models.
Most failed character chats trace back to a few mistakes. Know these before you hit them.
These are the failures people hit most often, with the usual cause and fix.
For model-behavior problems this list does not cover, our AI and machine learning coverage tracks which models changed and when.
Keyword overlap misses paraphrases. Store an embedding vector next to each fact and rank by cosine similarity, keeping the importance and recency terms. Our RAG chatbot tutorial with Python and LangChain walks through the vector side, and the concepts carry over to Node unchanged.
OpenRouter announced a Batch API on September 28, 2026 that typically costs half the standard per-token price and returns results within 24 hours. That fits a nightly job that rereads the day’s facts, merges near-duplicates and rewrites the summary, while live turns stay on the fast path.
OpenAI released GPT-Live-1, an API model for full-duplex voice conversations that supports custom voices and telephony. If you add voice, feed it the same persona file and the same retrieved facts. A voice agent with its own separate prompt is a second character wearing the first one’s name, and your drift suite will not catch it unless you test the voice path too.
Build every image prompt from the persona’s appearance field first, then add the scene. Never let the scene description overwrite the appearance, or Juno will gain and lose glasses between pictures. OpenAI announced ChatGPT Images 2.6 on November 4, 2026, and image models in general differ a lot in how well they hold a likeness, so run the same three prompts across candidates before you pick one.
When every step is done, your folder should match this tree. Anything missing points to the step that creates it.
Before you call it done, run through this list.
From here, the useful experiment is a model swap. Change CHAT_MODEL to each candidate in the table above, rerun the suite, and compare passes and session cost. You end up with a character that is yours rather than a feature inside someone else’s app.
It stores what you say outside the model. This project keeps raw messages, short fact rows and a running summary in SQLite, then rebuilds the prompt from those pieces on every turn. The model itself retains nothing between calls.
There is no single winner, so test. GPT-6.1 Terra, Claude Sonnet 5.5, Gemini 4 Argon and DeepSeek V4.1 Flash are all current options. Put each in the CHAT_MODEL slot, run the drift suite, and compare pass rates, tone and cost per session.
Under my planning assumptions, 1,000 chat turns on GPT-6.1 Terra cost about $4.25 without caching and about $2.35 with a cached persona prefix. The memory model adds a few cents. Measure your own usage with the API’s usage field, because prompt sizes vary.
Not to start. Keyword scoring over a few hundred facts works fine. Add embeddings when users start phrasing things in ways your keywords miss, and keep SQLite as the store.
Type /wipe and confirm. It removes every message, fact and summary for your user ID in one transaction. Use /forget key to remove a single fact.
The sample persona deliberately excludes it, and this guide does not cover it. If you go that way, you take on age verification, content policy and provider terms of service. Read your model provider’s usage rules first, since several restrict companion and adult use.
Nadia Dubois is the AI & Innovation Editor at Tech Insider, where she tracks the rapid evolution of artificial intelligence, from foundation models to real-world enterprise deployment. She previously covered AI and startups for La Tribune and contributed to MIT Technology Review's European coverage. Nadia specializes in generative AI, AI regulation, and the intersection of technology and European industrial policy. She holds a dual degree in Computational Linguistics and Journalism from Sciences Po Paris.
The Tech Insider Newsletter
Industry insights, the latest tech news, and special interviews, every week.

One email a week. No spam, unsubscribe anytime.
Tech Insider delivers in-depth coverage of the technologies shaping the future: AI, cybersecurity, cloud computing, hardware, and the trends that matter.
Tech Insider delivers in-depth coverage of the technologies shaping our future. From AI and cybersecurity to cloud computing and hardware innovation, our editorial team covers the trends that matter.
Tech Insider delivers in-depth coverage of the technologies shaping our future. From AI and cybersecurity to cloud computing and hardware innovation, our editorial team covers the trends that matter.
English | Svenska | Français | Suomi
The Tech Insider Newsletter: industry insights, tech news, and special interviews.

source

Scroll to Top