{"slug": "it-s-time-to-come-back-to-the-pot-peopleofdpast", "title": "It's time to come back to the POT - PeopleOfdPast", "summary": "A developer built People of the Past (POT), an AI application split into two experiences: Ghost Gallery, which grounds conversations with historical figures in separate historical profiles and source details, and TYLO, which lets users create AI memory companions from structured profiles of deceased loved ones covering life stages, personality, shared moments and values. The developer reports that each fix to the AI layer exposed a deeper problem, moving from basic conversation to memory, then retrieval, context, identity and privacy, and designed the system so personal family memories are stored separately from historical data with different grounding and privacy requirements.", "body_md": "I'm hosting the application on **Render's free tier**, so there's one little thing to keep in mind.\n\nIf the demo hasn't received any traffic for a while, the **backend service may have gone to sleep**. The first request can take a little longer while Render wakes it up.\n\nSo, if the application appears to be loading or the first API request takes time, **please give it a moment before refreshing the page.**\n\nIt's a **free-tier cold start**, not necessarily a broken demo. :)\n\nEvery time I thought I had figured out the AI part of this project, **the problem moved one layer deeper.**\n\nFirst, I needed a model that could hold a conversation. Then I realised **a conversation without memory was just another chatbot.** Once I started building memory, I had to think about retrieval, context, identity, privacy, and what happens when the AI confidently says something that was never actually told to it.\n\nAnd somewhere in between, a modal decided it wanted to live in the top-left corner of the screen. :)\n\nThis is the story of building **People of the Past (POT)**: the decisions that shaped it, the things that broke, the experiments that didn't go as planned, and the architecture I ended up with.\n\nPeople of the Past started with a simple idea:\n\n**What if we could have meaningful conversations with people from the past, while preserving the memories and stories that make those people special?**\n\nI eventually split that idea into **two experiences.**\n\nGhost Gallery lets users explore conversations with **notable historical figures.**\n\nThe challenge here isn't simply making an AI imitate a historical personality. **A convincing response that invents historical facts is not a good response.**\n\nI wanted the experience to be informed by **historical profiles and source details**, rather than relying entirely on whatever the model happens to know.\n\nThe historical profiles are stored separately from private TYLO companions. That distinction matters: **historical information and personal family memories have very different privacy and grounding requirements.**\n\nTYLO is the more personal side of the project.\n\nIt allows someone to create an **AI memory companion** using information they provide about a loved one who has passed away.\n\nInstead of asking users to write one enormous prompt, I built a **structured profile** that captures different dimensions of a person's life:\n\n| Section | Information collected | \n|---|---|\n| **Their life** | Life stages, relationship, family and friends, important dates | \n| **Their personality** | Nature, nicknames, speaking style, favourite phrases | \n| **Your shared world** | Special moments, stories, routines, interests and hobbies | \n| **What they believed** | Values, likes and dislikes, lessons and advice | \n\nA profile also includes a **photograph and an introduction.**\n\nThe goal is to give the system more than a name and a generic personality description. It needs the little details: **the nickname, the familiar phrase, the Christmas tradition, the way someone approached a difficult day.**\n\nThose details are what make a memory personal.\n\n**One distinction I want to make very clear:** TYLO does not bring someone back, and it does not recreate their actual consciousness. It generates conversations using information and memories supplied by the user. **It is a space for remembrance, not a replacement for the person.**\n\nMy earliest instinct was to think about the model first.\n\nWhich model should I use? How should I write the system prompt? How do I make the responses sound natural?\n\nBut I eventually ran into a much more important problem.\n\nImagine someone creates a companion and tells it:\n\nNow imagine the user returns after several conversations and asks about that Christmas tradition.\n\n**A model that only sees the latest message has no reliable way of recalling it.**\n\nSending every piece of profile information and the entire conversation on every request is not a sensible long-term solution either. **Context needs to be selected.**\n\nThat is where the **TYLO memory architecture** became the centre of the project.\n\nI stopped thinking of TYLO as simply a chatbot with a personality prompt. I started treating it as a system with several sources of context:\n\nThe model generates the response. **The memory system decides what information the model gets to work with.**\n\nThat separation changed how I approached the backend.\n\nThis is the current architecture behind a TYLO message.\n\nI'm using **MongoDB Atlas** for both the application data and the vector retrieval layer.\n\nThe main collections are:\n\n| Collection | Responsibility | \n|---|---|\n| `personas` | Published historical profiles and their source details | \n| `companions` | TYLO profiles, saved memories and summaries | \n| `tylomessages` | Persisted TYLO conversation messages | \n| `tylomemories` | Conversation exchanges and their 768-dimensional embeddings | \n\nThe distinction between `tylomessages` and `tylomemories` is intentional.\n\nThe message collection stores the conversation history. The memory collection provides a **retrieval-oriented representation** of conversation exchanges, including their embeddings.\n\nThis allows the application to maintain recent conversational context while also looking for **older, semantically relevant exchanges.**\n\nWhen a user sends a message, the API creates an **embedding for the question.**\n\nThe expected embedding size in this implementation is **768 dimensions.**\n\nThat vector is used to query the MongoDB Atlas Vector Search index:\n\n`tylo_memory_vector_index`\n\nThe current retrieval configuration is:\n\n| Setting | Value | \n|---|---|\n| **Vector index** | `tylo_memory_vector_index` | \n| **Expected vector dimensions** | **768** | \n| **Candidate documents** | **50** | \n| **Relevant exchanges returned** | **Up to 4** | \n| **Ownership filters** | `ownerSub` and`companionId` | \n| **Model compatibility filter** | `embeddingModel` | \n\n**Why retrieve 50 candidates if we only need up to four?**\n\nThe idea is to search a wider candidate set and then provide a smaller, relevant selection to the generation step. **The model doesn't need fifty old exchanges in its prompt. It needs the few that are useful for the current message.**\n\nThere is also an important reason for filtering on `embeddingModel`: vectors generated by different embedding models should not be casually treated as interchangeable. **Retrieval needs to respect the embedding model used to create the stored vectors.**\n\nThis is the part I wanted to make explicit because it is easy to say \"we use vector search\" without explaining what happens after the search.\n\nFor a typical TYLO message, the context is assembled from:\n\n| Context source | Maximum included | \n|---|---|\n| **Companion profile** | Relevant profile information | \n| **Recent conversation history** | **Latest 8 message records** | \n| **Saved memories** | **Up to 3** | \n| **Companion summary** | Current summary | \n| **Retrieved conversation exchanges** | **Up to 4** | \n\nThe result is a **bounded context assembled from several sources**, rather than blindly sending the entire conversation.\n\nThe memory retrieval step also has a fallback.\n\nIf vector retrieval fails, the API logs the failure and continues with the companion profile and recent conversation history.\n\nThat doesn't magically make missing memories available. It does, however, **prevent a retrieval failure from automatically turning into a completely failed conversation.**\n\nThe important part is that **memory creation and memory retrieval are separate stages.**\n\nA conversation exchange can be persisted first and processed for embedding in the background. On a later message, that exchange can become a candidate for semantic retrieval.\n\nThis is the foundation of **TYLO's longer-term memory behaviour.**\n\nThis was one partner decision I didn't want to treat as just another item in the technology stack.\n\nGhost Gallery deals with published historical information.\n\n**TYLO is different.**\n\nA user could upload a photograph of a loved one, describe their family relationships, share private experiences, explain personal traditions, and write down memories they may never have shared publicly.\n\nThat information deserves a **proper identity and access-control layer.**\n\nI chose **Auth0** because I needed to establish who was making a request before the API accessed their private companion data.\n\nOn the frontend, I'm using:\n\n`@auth0/auth0-react`\n\nOn the Express API, token validation is handled using:\n\n`express-oauth2-jwt-bearer`\n\nThe basic flow is:\n\nThis is especially important for vector search.\n\nConsider two users who have both created a companion profile for their grandmother. Their memories might contain similar words, names, family relationships or traditions.\n\n**A semantic search that ignores ownership could return a memory belonging to the wrong user.**\n\nThat is unacceptable for TYLO.\n\nThe vector retrieval query is therefore scoped using:\n\n`ownerSub`` companionId``embeddingModel`\n**The ownership identity must come from the validated token**, not from an arbitrary user identifier supplied by the browser.\n\nI also want to be precise about the security claims.\n\nAuth0 establishes and verifies identity and gives the API a trusted identity context. **It does not automatically encrypt every MongoDB record, enforce every database filter, or make the application end-to-end encrypted.**\n\nThose responsibilities still belong to the application's backend, database configuration and data-handling design.\n\nI have also not claimed to implement Auth0's more advanced AI-agent capabilities such as Token Vault, asynchronous authorization or fine-grained authorization unless those features are actually integrated.\n\nFor this project, the implemented value is the **identity and protected API layer around private AI conversations.**\n\nThat is why Auth0 mattered to the architecture rather than simply being another login button.\n\nNow for the model side.\n\nI wanted to experiment with **open-weight models** and run local inference during development. Ollama made it straightforward to pull a model and connect it to the application.\n\nThen I met the less exciting part of local AI development:\n\n**Hardware limitations.**\n\nI experimented with different local model options, but my laptop's available RAM made larger models uncomfortable to work with. I needed something practical enough to test repeatedly, not a model that turned every experiment into a hardware-management exercise.\n\nI settled on experimenting with **Gemma through Ollama**, including the `gemma4:e2b` model.\n\nIt wasn't about declaring one model universally better than everything else. It was about finding a practical model that allowed me to keep building and testing.\n\n**I did not fine-tune Gemma or retrain its weights.**\n\nThe improvements I worked on were at the application level:\n\nIn other words, I was improving **the information available to the model and the way the task was framed.**\n\nThis distinction is important.\n\nA model can produce a poor answer because its underlying capabilities are limited. But it can also produce a poor answer because the application supplied incomplete, irrelevant or misleading context.\n\n**Those are different problems, and they need different solutions.**\n\nThe backend is not tightly coupled to a single local Ollama process.\n\nThe inference architecture supports **Cloudflare Workers AI** when the required API credentials are configured. Otherwise, the local inference path uses Ollama through `MODEL_BASE_URL`, which defaults to:\n\n`http://localhost:11434`\n\nThis separation helped keep the API architecture flexible.\n\nIt also created an important deployment lesson: **Render does not automatically have access to the Ollama instance running on my laptop.**\n\nThe API service needs a reachable inference endpoint. Cloudflare credentials or another correctly configured model endpoint must be supplied to the deployed API.\n\nI don't want to claim that Render hosts Gemma inference. **It hosts the application services; inference is handled separately.**\n\nThe application is split into separate services.\n\n| Layer | Technology | Responsibility | \n|---|---|---|\n| **Frontend** | React + TypeScript + Vite | User interface and application experience | \n| **Styling** | Tailwind CSS | Layout and visual design | \n| **Routing** | React Router | Navigation between experiences | \n| **Identity** | Auth0 | Authentication and access-token identity | \n| **Backend** | Node.js + Express + TypeScript | API, validation and application logic | \n| **Database** | MongoDB Atlas | Profiles, messages and memories | \n| **Retrieval** | Atlas Vector Search | Semantic memory retrieval | \n| **Inference** | Cloudflare Workers AI / Ollama | Model inference through configured provider | \n| **Deployment** | Render | Frontend and API hosting | \n\nThe React application is served as a **Render static site.**\n\nThe Express backend runs as a **separate Render web service.**\n\nThis keeps the responsibilities clear:\n\n**Cloudflare credentials are not declared in `render.yaml`** in the architecture snapshot I'm documenting. They need to be supplied through the API service's environment configuration to use the Cloudflare path in production.\n\nOtherwise, the API falls back to the configured Ollama endpoint. On Render, `localhost` refers to the API service environment, not my personal laptop.\n\nIt sounds obvious once you draw the diagram. It is considerably less obvious when you're staring at a deployment that works locally and doesn't behave the same way remotely.\n\nI don't think **\"I sent a message and the model replied\"** is a meaningful test for a project like this.\n\nA memory companion can generate a warm, natural response and still get the important details wrong.\n\nFor TYLO, I wanted to look at whether the conversation reflected the profile, whether the memory architecture could supply relevant context, and whether the system respected the boundaries around private information.\n\nHere are some of the checks and observations that shaped the implementation.\n\nI created a sample companion profile with information across the four sections: personality, speaking style, favourite phrases, shared experiences, beliefs and routines.\n\nThen I tried a simple greeting.\n\nThe resulting conversation reflected the **positive, encouraging tone** described in the profile.\n\nThat was encouraging. It showed that the companion profile could influence the generated response rather than the model replying with a completely generic greeting.\n\nBut the response also included a **present-day detail that had not been supplied in the profile.**\n\nThat was an important observation.\n\nThe response sounded natural, but **sounding natural is not the same as being grounded in memory.**\n\nIt exposed a distinction I need to preserve throughout the project:\n\n**The system can use a person's personality and memories as conversational context, but it should not invent personal facts and present them as remembered experiences.**\n\nThat is an ongoing quality and prompt-design challenge, not something I will pretend has been completely solved.\n\nA conversation has two different memory requirements.\n\nThe first is **immediate continuity**: understanding what was said a few messages ago.\n\nThe second is **long-term recall**: finding something meaningful from an older exchange that is no longer in the recent message window.\n\nThe current context assembly addresses both:\n\nThe consequence is architectural: **an older relevant exchange does not have to be present in the latest eight messages to be considered for the next response.**\n\nI haven't produced a formal retrieval-accuracy benchmark, so I won't attach a made-up percentage to this improvement. The retrieval configuration is implemented; its quality still needs systematic evaluation across a larger set of real test cases.\n\nThis is one of the most important tests for TYLO.\n\nA normal search might find semantically similar memories across multiple users. But **TYLO cannot treat all stored memories as one shared pool.**\n\nThe retrieval query includes the **verified owner identity and companion identifier.**\n\nThat gives the memory search a defined ownership boundary, rather than relying on the model to decide which memories are appropriate.\n\nThe implementation uses `ownerSub` and `companionId` as filters.\n\nThe next level of verification here is a dedicated multi-user integration test: create separate users with similar companion profiles, seed similar memories, and verify that each user can retrieve only their own data. **The filters are part of the architecture, but I won't describe that as a completed security audit.**\n\nVector search is an additional source of context, not the only source.\n\nThe failure path logs a retrieval error and continues with the companion profile and recent history.\n\nThat gives the application a **graceful-degradation path.**\n\nIt is better to have a conversation with limited context than to silently pretend that missing memories were successfully retrieved.\n\nThe TYLO creation flow was also tested beyond just its appearance.\n\nThe companion creation process requires a photograph and the structured profile details. **Validation exists on the server as well as in the interface.**\n\nThat matters because frontend validation alone can be bypassed.\n\nThe image flow also needed attention: **image selection, JPEG compression, previewing the selected image and saving the resulting image data** with the companion document.\n\nThe current implementation stores the photo as image data in MongoDB. That is straightforward for an initial version, but it increases document payload size. **Moving images to dedicated object storage** is a sensible future improvement.\n\nI want to be honest about the level of verification.\n\n| Area | What has been established | What is still needed | \n|---|---|---|\n| **Profile creation** | Required photo and profile validation, including server-side validation | Broader browser and edge-case testing | \n| **Image handling** | Selection, compression, preview and persistence flow implemented | Evaluate object storage as usage grows | \n| **Conversation** | A sample profile-driven response was generated | Larger grounded-response evaluation | \n| **Memory retrieval** | Vector index, candidate retrieval, limits and filters are part of the architecture | Formal recall and relevance evaluation | \n| **Isolation** | Owner and companion filters are used for retrieval | Dedicated multi-user security integration tests | \n| **API** | Health endpoint returned successfully outside the restricted development environment | Full deployed request-path verification | \n| **Build** | `npm run build` passed at the documented development snapshot | Re-run after subsequent changes | \n| **Full AI conversation** | The application flow and a sample conversation are present | A complete end-to-end test across deployed Auth0, API, MongoDB and the configured inference provider | \n\nThis is not a claim that the system is production-audited or that retrieval accuracy has been scientifically measured.\n\n**It is a record of what works, what the design does, and what still deserves testing.**\n\nSome of the most frustrating problems had absolutely nothing to do with the model.\n\nWhile building the frontend, I ran into several regressions that reminded me how much work exists between a feature working in isolation and the application feeling coherent.\n\nA few examples:\n\n**The Explore Story page.** It initially displayed a large chat composer where I wanted a focused story experience. The selected historical figure also needed to load directly from its slug. The navigation and route handling had to be corrected so a figure could be opened reliably.\n\n**The Talk Now modal.** The modal had an issue with its lifecycle and could close unexpectedly. React Strict Mode exposed behaviour that wasn't as stable as I wanted. I had to fix the modal's interaction handling, including Escape and backdrop behaviour.\n\n**The modal position.** After a CSS reset, the modal appeared pinned to the top-left instead of sitting in the centre. It was one of those bugs where the code looked reasonable until the browser rendered it. **Explicit positioning fixed it.**\n\n**The TYLO layout.** The companion creation form was appearing before the existing memory spaces and conversations. That made the page feel like a setup screen even for someone who had already created companions. **Reordering the experience made the saved spaces and conversations easier to reach.**\n\n**Historical portraits.** Finding images for historical figures was another problem. A URL returning an image does not prove that the image depicts the right person. Some candidate images needed stricter filtering and manual review. **An unrelated film still or an incorrectly attributed portrait can undermine the whole experience.**\n\nI also used **Codex** to help inspect and fix targeted issues. It was useful for identifying regressions and making focused changes, but I still had to review the results and build the application again.\n\nAt one point, the changes had passed the build check, but the **full live AI conversation had not been verified end to end.** That distinction matters.\n\nA successful build tells me the code compiles. It doesn't tell me that Auth0, the database, vector retrieval and the deployed inference provider all cooperate correctly in a real conversation.\n\nI initially considered provisioning a **DigitalOcean Droplet** to run model inference.\n\nThe idea was straightforward: host the model separately and have the API communicate with it.\n\nBut I got stuck at the billing setup and, honestly, **I was too lazy to finish the provisioning process** at that stage.\n\nSo I moved forward with the architecture I could actually work with: **Render for the web application and API, MongoDB Atlas for persistence and retrieval, and a configurable inference provider.**\n\nIt wasn't some grand technical decision after an extensive infrastructure benchmark.\n\nSometimes the engineering story is simply that **the infrastructure you planned is not the infrastructure you end up using.**\n\nI am not claiming a DigitalOcean integration, because I did not provision one.\n\nI didn't want to select partner categories just because a technology appeared somewhere in the dependency list.\n\n**Each one needs to have a meaningful responsibility in the application.**\n\nMongoDB Atlas is the **persistence and memory-retrieval layer.**\n\nIt stores the historical profiles, TYLO companions, conversation messages and memory exchanges. Atlas Vector Search supports semantic retrieval over the **768-dimensional memory embeddings.**\n\nThe `tylo_memory_vector_index` index, candidate limit, returned exchanges and owner/companion filters are part of the actual memory architecture.\n\nWithout this layer, TYLO would have a much weaker approach to **long-term conversational recall.**\n\nRender hosts **two distinct application services:**\n\nThe API handles protected requests, database operations, context assembly and communication with the configured model provider.\n\n**Render is responsible for hosting the application services. It is not the machine running my laptop's local Gemma model.**\n\nTYLO works with **private, user-provided information.**\n\nAuth0 establishes the authenticated identity used by protected API requests. The Express backend validates the access token, reads the user's subject and uses that identity when accessing companion and memory data.\n\nThe relationship between identity and memory retrieval is the important part here.\n\n**A user should not be able to access another person's companion simply because a vector search finds similar text.**\n\nI'm describing the Auth0 functionality actually integrated: **authentication, access-token validation and protected API access.** I'm not claiming Token Vault, asynchronous authorization or fine-grained authorization features that are not part of the current implementation.\n\nGemma was part of my **local model experimentation through Ollama.**\n\nThe hardware limitations on my laptop influenced the model I could practically use during development. I then iterated on the application-side instructions and context assembly to improve the quality of the conversations.\n\n**I did not fine-tune Gemma.**\n\nOne important submission detail: the deployed inference provider is configurable. I will make sure the submitted demo's actual model configuration supports the Gemma category claim. **A local development experiment alone should not be presented as proof that the deployed demo is currently running Gemma.**\n\nI would rather enter fewer categories with a defensible technical explanation than list every partner name without meaningful usage.\n\nOne of the most interesting parts of this project was being able to experiment with **open-weight models** rather than treating the AI layer as an invisible external service.\n\nUsing Ollama gave me a practical local development path. I could experiment with a model, modify the application prompt, change the context being supplied and inspect the response.\n\nThat made the model layer feel like something I could investigate and improve around.\n\nBut there is a distinction I want to preserve:\n\n**Open-weight model experimentation does not automatically make an application fully local or private.**\n\nPOT also uses hosted infrastructure. **MongoDB Atlas stores application data, Render hosts the application services, and the configured inference provider may be cloud-based.**\n\nThe system's privacy and data-handling properties depend on the actual deployment and configuration, not simply on whether the model's weights are open.\n\nThat is especially important for TYLO.\n\nThere are several things I would like to take further.\n\n**A proper memory evaluation suite.** I want a repeatable collection of questions where the correct answer depends on an older profile detail or a previous conversation. I would measure whether the right exchange is retrieved, whether irrelevant memories are excluded and whether the generated answer stays within the available evidence.\n\n**Stronger grounding behaviour.** The sample conversation showed that a warm response can still introduce unsupported details. I want the companion to distinguish between something explicitly remembered, a reasonable conversational response and something it simply does not know.\n\n**A dedicated privacy test suite.** Multi-user tests should verify that similar companion profiles and overlapping memories never cross ownership boundaries. I also want broader tests around protected routes and companion access.\n\n**Object storage for photographs.** Storing compressed image data directly with companion documents is convenient at this stage, but dedicated object storage will be a better direction as the number of users and images grows.\n\n**Historical source quality.** Ghost Gallery needs a stronger process for reviewing profile information and portrait attribution. A historical conversation should not sound confident about information that is uncertain or unsupported.\n\n**A more complete deployed integration test.** I want to verify the full path—from Auth0 authentication to the Express API, MongoDB retrieval, configured model inference and the streamed response—in the actual deployed environment.\n\nThe most satisfying moment wasn't getting an API endpoint to return a response.\n\nIt was seeing a **structured companion profile turn into an actual conversation.**\n\nA photograph, a relationship, a collection of everyday details, favourite phrases and stories—all of that information started to become useful conversational context.\n\nIt made me think differently about what a memory system needs to do.\n\nIt also made me more cautious about what it should say.\n\n**A convincing imitation is not necessarily a respectful one.** The system needs to be thoughtful about the difference between what a person actually shared and what a language model is capable of inventing.\n\nThat is probably the biggest lesson I am taking away from this build.\n\n**The AI part is exciting. The engineering around it is what makes the experience meaningful.**\n\nAnd there is still a lot I want to get right.", "url": "https://wpnews.pro/news/it-s-time-to-come-back-to-the-pot-peopleofdpast", "canonical_source": "https://dev.to/bluestars/its-time-to-come-back-to-the-pot-peopleofdpast-29el", "published_at": "2026-11-01 04:15:09+00:00", "updated_at": "2026-11-01 04:21:53.737800+00:00", "lang": "en", "topics": ["artificial-intelligence", "ai-agents", "ai-products", "generative-ai"], "entities": ["People of the Past", "Ghost Gallery", "TYLO", "Render"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/it-s-time-to-come-back-to-the-pot-peopleofdpast", "markdown": "https://wpnews.pro/news/it-s-time-to-come-back-to-the-pot-peopleofdpast.md", "text": "https://wpnews.pro/news/it-s-time-to-come-back-to-the-pot-peopleofdpast.txt", "jsonld": "https://wpnews.pro/news/it-s-time-to-come-back-to-the-pot-peopleofdpast.jsonld"}}