One thing I wanted REXA to eventually have was long-term memory.
The idea is simple: when I explicitly tell REXA something like:
βRemember that I prefer PostgreSQL for my backend projects.β
REXA should be able to save that information so it can be used later.
The important distinction is that REXA does not currently retrieve these memories yet. Today, the implemented part is the memory-saving pipeline. Retrieval is the next part I plan to build.
I also didn't put the memory database directly inside the REXA CLI.
Instead, I built the memory infrastructure behind the REXA website backend and let the CLI communicate with it through an authenticated HTTP API.
The architecture looks like this:
REXA CLI
β
β POST + Bearer Token
β { "text": "..." }
βΌ
REXA Website Backend
β
βββ Verify Token
βββ Identify User
βββ Process Text
βββ Generate Embeddings
βββ Store Memory
β
βΌ
PostgreSQL + pgvector
One important design decision is that REXA does not automatically save every preference or every piece of conversation.
Instead, the model uses a tool called:
save_memory
When the user explicitly asks REXA to remember something, the model calls this tool.
For example:
User:
Remember that I use Bun for my backend projects.
REXA can decide that the appropriate action is to call:
save_memory(...)
So memory creation is currently explicit, rather than REXA silently storing everything the user says.
save_memory
The save_memory tool receives the text that the user wants to store.
Before sending it to the backend, the CLI performs some basic validation.
The CLI:
This means extremely large documents may never leave the CLI in the first place.
For normal memory entries, however, the text can then be sent to the backend.
The REXA CLI does not directly access PostgreSQL or pgvector.
Instead, it sends a POST request to the REXA backend:
POST https://rexa-server.onrender.com/api/cli/memory
Authorization: Bearer <token>
Content-Type: application/json
The request body contains only the memory text:
{
"text": "I prefer PostgreSQL for my backend projects."
}
Notice that there is no userId in the request body.
The identity of the user comes from the authentication token.
The token comes from the REXA CLI login flow.
The CLI first authenticates through the REXA backend using:
POST /api/cli/verify
The same Bearer-token mechanism is then used when making authenticated CLI requests.
This means the CLI doesn't simply tell the backend:
{
"userId": "123",
"text": "..."
}
Instead, it says, effectively:
Here is my authentication token.
Here is the memory I want to save.
The backend is responsible for determining who that token belongs to.
When the memory request reaches:
/api/cli/memory
the backend first checks the Bearer token.
If the token is missing, invalid, or expired, the request is rejected.
The CLI can surface these authentication failures and provide a hint to run:
rexa login
This makes the authentication layer separate from the actual memory-processing logic.
After successful token verification, the backend knows which authenticated user is making the request.
This is important because memory is per user.
The CLI doesn't provide the user identity manually.
Instead:
Bearer Token
β
Token Verification
β
Authenticated User
β
Memory belongs to that user
This prevents the client from simply claiming that a memory belongs to some other user.
Once authentication succeeds, the backend receives the text.
"I prefer PostgreSQL for my backend projects."
From here, the rest of the pipeline happens on the backend.
This is where the database and embedding infrastructure come into play.
The CLI doesn't need to know how the backend implements this processing.
Conceptually, the pipeline is:
Text
β
Chunking
β
Batch Creation
β
Embedding Generation
β
Vector
β
PostgreSQL + pgvector
These are backend responsibilities.
The backend can split larger pieces of text into smaller chunks.
Conceptually:
Large Text
β
βββ Chunk 1
βββ Chunk 2
βββ Chunk 3
βββ ...
For a tiny memory such as:
"I prefer PostgreSQL."
there may not be much to split.
But chunking becomes useful as the amount of stored information grows.
After chunking, the backend can group chunks into batches for embedding.
Chunks
β
βββ Batch 1
β βββ Chunk 1
β βββ Chunk 2
β βββ Chunk 3
β
βββ Batch 2
βββ Chunk 4
βββ Chunk 5
βββ Chunk 6
These batches are then passed to the embedding stage.
The text is converted into a numerical vector using an embedding model.
"I prefer PostgreSQL for my backend projects."
β
βΌ
Embedding Model
β
βΌ
[0.12, -0.38, 0.74, ...]
The resulting vector represents the semantic information contained in the text.
This is what makes vector-based memory possible.
Instead of treating a memory as only a string of characters, the backend also stores a mathematical representation of its meaning.
The generated vector is stored using pgvector alongside the memory data.
The database layer uses:
PostgreSQL
+
pgvector
+
Prisma
A simplified representation might look like:
Memory
βββββββββββββββββββββββββββββ
user_id
text
embedding
created_at
...
The exact schema can evolve, but the important part is that the memory is associated with the authenticated user and has a vector representation that can later be used for semantic retrieval.
Once the memory has been successfully processed and stored, the API returns a success response.
The actual response from the controller is:
{
"success": true,
"message": "Data saved in memory"
}
The CLI can then use that result to tell the user that the memory was saved successfully.
So the complete flow is:
User
β
β "Remember this..."
βΌ
REXA model
β
β calls save_memory
βΌ
REXA CLI
β
β validate + trim text
β
β POST /api/cli/memory
β Authorization: Bearer <token>
β { "text": "..." }
βΌ
REXA Backend
β
β verify token
β identify user
β
β process memory
β βββ chunk
β βββ batch
β βββ embed
β βββ store vector
βΌ
PostgreSQL + pgvector
β
β success
βΌ
REXA Backend
β
β { "success": true,
β "message": "Data saved in memory" }
βΌ
REXA CLI
The main architectural decision was to keep REXA itself separate from the memory infrastructure.
The CLI is responsible for interacting with the agent and invoking save_memory.
The backend handles authentication and memory processing.
PostgreSQL and pgvector provide the persistent storage and vector representation.
So the responsibilities are roughly separated like this:
REXA CLI
β Agent interaction + save_memory
Backend
β Authentication + memory processing
PostgreSQL + pgvector
β Persistent memory storage
This also means the CLI doesn't need database credentials or direct database access.
It only needs an authenticated API connection.
At the moment, the implemented functionality is the save side of memory.
User
β
save_memory
β
Authenticated API
β
Embedding
β
Vector storage
The retrieval side is not implemented in the CLI yet.
The next step is to build the other half:
Current:
SAVE
User
β
save_memory
β
Backend
β
Vector Database
Future:
RECALL
Current Task
β
Memory Search
β
Similarity / Relevance
β
Relevant Memories
β
REXA
That is where things become much more interesting.
The goal isn't simply to give REXA a database full of memories.
The real goal is to build a system where REXA can eventually find the right memory when it is actually useful.
And that's the part I'm building next.