Build a Memory Layer for an AI Astrology Companion
An AI astrology companion needs memory to retain users. Keep it on your stack, not the vendor side, with a stateless API. Architecture and code inside.
TL;DR
- What turns an astrology chatbot into a companion is memory: it remembers the user chart, past readings, and how they reacted.
- Keep that memory on your own stack, not the vendor side. A stateless API computes the facts; your database holds the relationship.
- The pattern is simple: store the chart once, log every reading, optionally add vector recall, then assemble context per turn and call your model.
- The calculation engine stays stateless and stores nothing. See the RoxyAPI Astrology API.
A horoscope chatbot answers one question and forgets you. A companion remembers. It knows your chart without asking again, recalls what it told you last week, and notices when this month contradicts last month. That memory is the difference between an app people try once and an app people keep, and it is entirely yours to build. The calculation can be stateless and outsourced; the memory should be stateful and yours. This post lays out the architecture and the code to get there.
Why should memory live on your side, not the vendor side?
Because the memory is your moat and your liability, and you want control of both. RoxyAPI is stateless by design: it computes from the inputs you send and returns a result, storing no birth data, no journals, no history. That is deliberate. It means every piece of user state you accumulate lives in your stack, so you own the personalization layer outright and no third party processor sits between you and your users data.
The ownership cuts two ways and both favor you. On defensibility, the six months of history a user builds with your companion is exactly what a competitor cannot clone by signing up for the same API. On privacy, sensitive content like journals and moods never leaves your control, so your compliance story stays simple. The build versus buy logic behind this split is in the API is not your moat.
The compounding is the point. A user with months of history will not restart with an identical-looking app that knows nothing about them, so every day of memory you accumulate raises the cost of leaving. That switching cost is the moat the API can never hand you, because the API returns the same answer to your competitor too.
Keep the calculation stateless and the memory yours. Start with the RoxyAPI Astrology API.
What user state does an astrology companion need?
Less than you think to start, and it grows naturally. Four layers cover almost every companion:
- Chart context. The natal chart, computed once from immutable birth data and stored. Every later turn reads it from your store, never the API.
- Reading history. Every reading you have shown, with a timestamp. This is what lets the companion say last month your focus was career, this month it shifts.
- Preferences. What the user cares about, their tone, the topics they engage with. Learned over time from behavior.
- Conversation and optional journaling. Recent turns for continuity, and any mood or journal entries the user chooses to add.
The first two are enough for a companion that feels alive. The rest are upgrades you layer in as you grow. Start with two tables, chart context and reading history, and let everything else be additive. You do not need a grand schema on day one. You need the two tables that make the companion feel like it remembers, and room to grow into preferences and journaling later.
How do you architect the memory layer?
As a clean join between a stateless calculator and a stateful store. Keep the two responsibilities separate and the design stays simple:
| Layer | Owner | Holds |
|---|---|---|
| Calculation | RoxyAPI, stateless | charts, transits, numerology, panchang |
| Identity and memory | Your database | user, birth data, preferences |
| Reading history | Your database | every reading shown, timestamped |
| Semantic recall | Your vector store | embeddings of past readings and chat |
At request time, the companion is the join: pull the stored chart and the relevant history from your store, fetch any live calculation you need from the stateless API, and hand all of it to your model as grounded context. The API supplies fresh facts; your store supplies continuity. The full step-by-step build, including vector recall, is in build an AI companion with memory.
Three rules keep this clean as it grows. Never send the API anything it does not need to compute, because it does not need journals or moods to return a chart. Always read immutable results from your store instead of recomputing them. And treat the model as a composer over grounded facts, never as a source of facts. Break the first and you leak user data, break the second and your bill balloons, break the third and the companion starts inventing astrology.
How do you keep the deterministic calls cheap?
By computing immutable things once. A natal chart never changes, so it is a single API call for the life of the user. Store it on first sight and read it from your database forever after. Only genuinely time dependent calls, like current transits, hit the API again.
async function ensureChart(userId: string, birth: BirthData) {
const cached = await db.charts.find(userId);
if (cached) return cached;
const chart = await roxy.astrology.natalChart(birth); // one call, ever
await db.charts.save(userId, chart);
return chart;
}
This is the same idea as caching deterministic results, scoped to a user, and it keeps your bill flat as your user base grows. The general version is in caching to cut your API bill.
How do you add semantic recall?
Row-by-row history answers what did we say on a given date. Semantic recall answers what have we discussed about this theme, which is what makes a companion feel like it remembers you rather than logs you. Embed each reading and each user message as a vector, store it keyed by user, and at request time embed the new message and pull the few most similar past entries.
const vector = await embed(renderedReading);
await vectors.upsert({ id, userId, vector, meta: { date } });
// on a new question
const related = await vectors.query(await embed(userMessage), {
filter: { userId },
topK: 4,
});
Those nearest entries go into the model context alongside the chart, so when a user asks about relationships the companion surfaces the last few relationship readings instead of whatever happened most recently. It is an upgrade, not a prerequisite: ship the timestamped history first, then add vectors when theme-level recall earns the extra infrastructure.
What does the per-turn flow look like in code?
Gather state, fetch live facts, then let your model compose the reply:
const chart = await ensureChart(userId, birth); // your store
const history = await recallReadings(userId, userMessage); // your store
const today = await roxy.astrology.transits(birth); // live, stateless
const reply = await llm.complete({
system: "You are a warm astrology companion. Ground every claim in the data.",
context: { chart, history, today },
message: userMessage,
});
await db.readings.append(userId, { date: today.date, shown: reply });
The structured fields from RoxyAPI are the grounding; your store is the memory; your model and prompt are the voice. Clone a working starting point from the open-source starters.
FAQ
Does RoxyAPI store conversation history or user memory?
No. The API is stateless and keeps no birth data, readings, or profiles. All memory lives in your own database and optional vector store, which keeps you in control of both personalization and privacy.
Where should an AI astrology companion store user data?
In your own stack: a database for identity, birth data, and reading history, and optionally a vector store for semantic recall. The API supplies calculations on demand; your store supplies the continuity that makes the companion feel like a relationship.
How do I avoid recomputing a user chart on every message?
Compute the natal chart once from the immutable birth data and persist it, then read it from your store on later turns. Only time dependent calls like current transits hit the API again, which keeps cost flat as you scale.
Do I need a vector database to add memory?
No. A database of reading history with timestamps is enough for a strong companion. A vector store adds semantic recall of themes across past readings, which is an upgrade rather than a requirement.
Is it safe to send journals or moods to the API?
Send only what a calculation needs, like date, time, and coordinates. Keep journals, moods, and chat history in your own store. The API is stateless and does not need that content, so sensitive data never leaves your control.
Conclusion
A companion is a calculator plus memory, and the memory is the half that retains users and belongs to you. Let a stateless API handle the verified calculation and build the chart context, reading history, and recall on your own stack. Start with the Astrology API, follow the full build in the companion tutorial, and check pricing for the all-in-one plans.