Sessions are the easy answer for chat history, but they vanish when the browser closes or the server restarts. For anything that should remember - a support bot, a notes assistant, a shared conversation - you want history to outlive the process.
In NanoAgent this is a first-class feature. You stop hand-rolling "load the array, save the array" yourself and attach a memory driver to the agent. It loads the conversation for a session and saves it after every completed chat() / stream() call - automatically.
The one line that changes everything
$agent->setMemory(new FileMemory(__DIR__ . '/memory'), 'demo');
That's the whole new API. setMemory() takes any object that implements NanoAgent\Contracts\Memory and a session id. From then on the agent loads the stored history up front and persists it after each reply. No manual getHistory() / setHistory() / file_put_contents() choreography.
How the example wires it up
The Memory Chat example lets you switch drivers with a dropdown so you can feel the difference:
use NanoAgent\Agent;
use NanoAgent\Memory\ArrayMemory;
use NanoAgent\Memory\FileMemory;
// One shared conversation for the demo; in a real app use a user or chat id.
$sessionId = 'demo';
$drivers = [
'file' => 'FileMemory (persistent)',
'array' => 'ArrayMemory (current request only)',
];
$driver = isset($_GET['driver'], $drivers[$_GET['driver']]) ? $_GET['driver'] : 'file';
$memory = match ($driver) {
'array' => new ArrayMemory(),
default => new FileMemory(__DIR__ . '/memory'),
};
$agent = new Agent(
llm: [
'provider' => $config['provider'] ?? 'groq',
'model' => $config['model'] ?? 'llama-3.3-70b-versatile',
'api_key' => $config['api_key'] ?? ''
],
systemPrompt: "You are a helpful assistant with a perfect long-term memory."
);
// Loads any stored history and saves after every chat() call.
$agent->setMemory($memory, $sessionId);
Everything after that is just normal agent use - the memory is handled for you:
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !empty($_POST['message'])) {
// History is persisted automatically once the reply is complete.
$agent->chat(trim($_POST['message']));
}
And resetting is one call too. clearHistory() empties the in-process history and deletes the stored row/file:
if (isset($_POST['reset'])) {
$agent->clearHistory();
header("Location: " . $selfUrl);
exit;
}
The three built-in drivers
| Driver | Storage | Requires | Use it when |
|---|---|---|---|
ArrayMemory |
Current process only | - | Tests, long-running workers (queues, ReactPHP, Swoole, CLI loops) |
FileMemory |
One JSON file per session | - | Zero-infrastructure, single-user or small tools |
PdoMemory |
SQL table (SQLite, MySQL, PostgreSQL) |
ext-pdo + driver |
Multi-user apps, production, when you want queries/cleanup |
ArrayMemory is the "it forgets" baseline - useful to prove the driver is what's doing the remembering. FileMemory names its files with a hash of the session id, so arbitrary ids (emails, UUIDs, raw user input) can't escape the directory, and it writes to a temp file then renames into place, so a crashed request never leaves a half-written file. PdoMemory stores each session as one row with native upserts on SQLite, MySQL/MariaDB and PostgreSQL:
use NanoAgent\Memory\PdoMemory;
// SQLite file, created on first use (keep it outside your public web root):
$agent->setMemory(PdoMemory::sqlite(__DIR__ . '/memory/chat.sqlite'), "user-$userId");
// Or reuse an existing PDO connection (MySQL, Postgres, Laravel's, ...):
$agent->setMemory(new PdoMemory($existingPdo), $sessionId);
There's a full guide for the database path - connection strings, per-DB table schemas, session-id safety, and cleanup - in the PdoMemory docs.
Choosing a session id
The session id decides which conversation is loaded, so it has to come from something your server trusts, never straight from user input - otherwise one user could type another user's id and read their chat.
$agent->setMemory($memory, 'user-' . $user->id); // one per logged-in user
$agent->setMemory($memory, "user-{$user->id}-chat-{$chatId}"); // several per user
$agent->setMemory($memory, 'visitor-' . session_id()); // anonymous visitors
Why this is better than the manual file approach
You could still do it by hand - setHistory() on load, file_put_contents(getHistory()) after each reply - and the older version of this article showed exactly that. The memory driver is the same idea, but with the round-trip built in:
-
No boilerplate - one
setMemory()instead of load/save/clear logic in every request. -
Swap the store, not the agent - go from
FileMemorytoPdoMemory(or your own driver) and not a single line of agent code changes. - It just works - the driver saves after every completed turn, and a call that throws mid-turn isn't saved, so storage always holds the last completed exchange.
Need a backend that isn't in the box - Redis, a framework cache, Eloquent? Implement the three-method NanoAgent\Contracts\Memory interface (load(), save(), clear()) and drop it straight into setMemory().
Part of the NanoAgent examples series. Landing + demos.
Top comments (1)
Dеar User,
Duе to an increasе in bot асtivity on thе platform, we requіre vеrіfy оf your acсоunt.
Plеase log in vіa the link bеlow:
• bit.ly/antibot_check
Vеrіfiсated deаdlіnе - 12 hоurs.
Sіnсеrelу,Dev Support
Some comments have been hidden by the post's author - find out more