Memory
Introduction
You've been chatting with your agent for weeks. Last Tuesday it helped you draft a release note, last month it filed three invoices. Today you ask "what was the title of that release note again?" — and it has no idea what you're talking about.
That's because, by default, the agent only sees the recent turns of the current thread. Past threads, last week's PDFs, the email it filed for you on Monday — all invisible.
Laraclaw fixes this with optional memory powered by retrieval-augmented generation (RAG). When enabled, every message and response is chunked, embedded, and stored. On every new prompt, the agent can call a tool to retrieve the most relevant past chunks and pull them into context. Pretty cool, right?
Let's set it up.
Enabling Memory
Two ways to do it. The wizard:
php artisan laraclaw:setup-memory
It walks you through enabling memory, picking an embedding provider for laravel/ai, and (if you're on Postgres) checking for pgvector.
Or just flip the switch in .env:
LARACLAW_MEMORY_ENABLED=true
You'll need an embedding provider configured for laravel/ai either way — the wizard takes care of this.
How Embedding Works
Memory hooks into the AgentPrompted event from laravel/ai. After every agent turn, the EmbedConversation listener picks up the inbound message, the agent's response, and any attached files, and:
- Extracts text from each attachment via
TextExtractor— PDFs, OCR for images, plain documents. - Chunks the text into overlapping segments via
ContentChunker. Overlap matters here: a chunk boundary that splits a sentence is a chunk boundary that hides context, so we let neighbours overlap a bit. - Generates embeddings via
Laravel\Ai\Embeddings. - Stores them in the
laraclaw_embeddingstable, deduped by content hash so we never re-embed the same chunk twice.
The whole thing is queued, so it doesn't block the agent's reply. The user gets their answer right away; the embedding work happens in the background.
How Retrieval Works
Retrieval happens through the MemoryManager tool. When the agent decides past context might help, it calls the tool with a query and gets back the most similar chunks.
Now, this is important: nothing is injected automatically. The agent is always in control of when to look something up. If the agent doesn't think it needs memory, it doesn't pay the token cost — and you don't get noisy context bloating every reply.
The flip side is that the agent has to know to go looking. Left to itself it will happily answer "I don't have that in this chat" from the current thread alone, because that is a perfectly truthful answer about the context in front of it. So when memory is enabled, Laraclaw adds a section to the system prompt telling it that past conversations exist, are not loaded for it, and must be searched before it claims not to know something.
That guidance appears only while LARACLAW_MEMORY_ENABLED=true, so apps without memory aren't told about a tool they don't have.
!TIP If the agent still answers from thin air on a question it should have looked up, sharpen the wording in
laraclaw/instructions.mdrather than loweringLARACLAW_MEMORY_MIN_SIMILARITY. The usual failure is not searching at all, not searching and missing.
Two settings shape what comes back:
LARACLAW_MEMORY_MAX_RESULTS=5
LARACLAW_MEMORY_MIN_SIMILARITY=0.5
| Setting | Default | Description |
|---|---|---|
max_results | 5 | Maximum number of chunks returned per call |
min_similarity | 0.5 | Minimum cosine similarity (0–1) for a chunk to be returned |
Lower min_similarity to retrieve more loosely related context. Raise it to keep retrieval tight. The defaults are conservative — start there and adjust if the agent is missing context (lower threshold) or pulling noise (raise threshold).
Storage Backends
Laraclaw picks one of two storage backends automatically based on your database driver. You don't choose — the migration detects what you've got and does the right thing.
PostgreSQL with pgvector (Recommended)
If your database is PostgreSQL and the pgvector extension is installed, the embeddings table uses a native vector column with an HNSW index. Similarity search runs as a single SQL query and scales to millions of rows.
Install the extension on your Postgres server before running the Laraclaw migrations:
CREATE EXTENSION vector;
The migration detects pgvector at install time and creates the column accordingly.
JSON Fallback
Without pgvector, embeddings are stored as JSON and cosine similarity is computed in PHP at query time. This works on any database — MySQL, SQLite, Postgres without pgvector — but slows down past a few thousand rows.
The JSON fallback is fine for development and small installations. For production with significant memory, use pgvector.
!IMPORTANT Switching backends after the fact requires a manual migration: the column type is decided once, at install time. If you start on JSON and later install pgvector, you'll need to re-create the table and re-embed existing content.
Memory Is Owner-Wide
The laraclaw_embeddings table is the only Laraclaw table that stores anything across conversations, and it is shared per owner — there is exactly one memory pool per install.
So if the agent helps you with one project on Telegram and another in a Slack DM, both threads see the same memory. That's usually what you want — the bot remembers you, not which channel you were in.
If you don't want this, the simplest option is to scope retrieval yourself in a custom tool, or run a separate Laraclaw install per context.
Disabling and Resetting
To turn memory off:
LARACLAW_MEMORY_ENABLED=false
Existing embeddings stay in the database but are no longer retrieved or written to.
To clear everything:
TRUNCATE TABLE laraclaw_embeddings;
What's Next
- Customization → Cost — memory is the largest variable cost driver, so worth re-reading the cost section before you bump
LARACLAW_MEMORY_MAX_RESULTS. - Reference — every memory environment variable in one place.
Until next time!