1. Docs
  2. What To Build
  3. AI Companion with Memory

Build an AI companion with memory

RoxyAPI is stateless on purpose. This tutorial shows how to add per-user memory, chart context, reading history, and semantic recall, on your own stack, so your users data never leaves your control.

The thing that turns a horoscope toy into a companion users return to is memory: it remembers their chart, what it told them last week, and how they reacted. RoxyAPI deliberately stores none of that. It computes from the inputs you send and returns a result. The memory layer is yours to build, which is exactly what keeps you in control of personalization and privacy. This tutorial wires a simple but real memory layer around stateless RoxyAPI calls.

The principle: stateless calc, stateful you

Keep a clean separation. RoxyAPI answers "what are the facts for this birth data and date" deterministically. Your store answers "who is this user and what have we said before." The companion is the join of the two at request time.

LayerOwnerHolds
CalculationRoxyAPI (stateless)charts, transits, numerology, panchang
Identity and memoryYour databaseuser, birth data, preferences
Reading historyYour databaseevery reading you have shown
Semantic recallYour vector storeembeddings of past readings and chat

What you need

  1. A RoxyAPI key from the pricing page.
  2. An LLM key for the conversational layer.
  3. A database (Postgres, SQLite, or any KV) and, optionally, a vector store for semantic recall.

The fastest starting point is the open-source chatbot starter; this tutorial adds the memory layer on top of that integration shape.

Step 1: store the chart once

Birth data is immutable, so compute the chart a single time and persist it. Every later turn reads it from your store, never the API.

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 billable call, ever
  await db.charts.save(userId, chart);
  return chart;
}

This is the same caching idea as in the caching guide, scoped to a user.

Step 2: persist reading history

Every reading you show, save it with a timestamp. This is what lets the companion say "last month your focus was career, this month it shifts to relationships."

await db.readings.append(userId, {
  date: today,
  kind: "daily-horoscope",
  data: horoscope,        // the structured RoxyAPI response
  shown: renderedText,    // what the user actually saw
});

Step 3: add semantic recall

For a companion that remembers themes, not just rows, embed each reading and store the vector. At request time, embed the user current message and pull the few most similar past readings to ground the reply.

const vector = await embed(renderedText);
await vectors.upsert({ id, userId, vector, meta: { date: today } });

// later, on a new question
const recent = await vectors.query(await embed(userMessage), {
  filter: { userId },
  topK: 4,
});

Step 4: assemble context and reply

On each turn, gather the stored chart, the relevant past readings, and any live calculation you need, then hand all of it to your LLM as grounded context. RoxyAPI supplies fresh facts (today transits, a new tarot pull); your store supplies continuity.

const chart = await ensureChart(userId, birth);          // from your store
const memory = await recallReadings(userId, userMessage); // from 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, memory, today },
  message: userMessage,
});

Keep it private by design

Send the API only what a calculation needs (date, time, coordinates, a name for numerology). Do not pass journal entries, mood logs, or chat history to RoxyAPI. Those belong in your store. The API is stateless and keeps nothing, and your memory layer should be the only place sensitive user content lives.

FAQ

Does RoxyAPI store conversation or user memory?

No. The API is stateless and keeps no birth data, history, or profiles. All memory lives in your own database and vector store, which keeps you in control of privacy.

Where should per-user memory live?

In your stack: a database for identity, birth data, and reading history, and optionally a vector store for semantic recall. RoxyAPI supplies the calculations; your store supplies continuity.

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 live, time-dependent calls like current transits hit the API again.

Can I build a companion without a vector store?

Yes. A database of reading history with timestamps is enough for a strong companion. A vector store adds semantic recall of themes, which is an upgrade, not a requirement.

Next steps

Reduce repeat calls with caching, keep your interpretation layer yours with the calculation engine guide, and start from the chatbot starter.