The persistence layer provides flexible storage for conversations, learning data, and configuration.
Generic key-value storage:
interface StorageInterface
{
/**
* Save data to storage.
*/
public function save(string $key, array $data): void;
/**
* Load data from storage.
*/
public function load(string $key): ?array;
/**
* Delete data from storage.
*/
public function delete(string $key): void;
/**
* List keys matching a pattern.
*/
public function list(string $pattern = '*'): array;
/**
* Check if key exists.
*/
public function exists(string $key): bool;
}Specialized for conversation history:
interface ConversationStorageInterface extends StorageInterface
{
/**
* Save a conversation thread.
*/
public function saveThread(string $threadId, array $messages): void;
/**
* Load a conversation thread.
*/
public function loadThread(string $threadId): array;
/**
* Append messages to a thread.
*/
public function appendToThread(string $threadId, array $messages): void;
/**
* List all thread IDs.
*/
public function listThreads(): array;
/**
* Delete a thread.
*/
public function deleteThread(string $threadId): void;
}Stores data in the filesystem using JSON for structured data and Markdown for human-readable content.
class FileStorage implements StorageInterface
{
public function __construct(
protected string $basePath,
) {}
}storage/
├── conversations/
│ ├── thread-abc123.json
│ └── thread-def456.json
├── learning/
│ ├── patterns/
│ │ ├── filesystem-read_file.md
│ │ └── database-query.md
│ └── bugs/
│ ├── bug-2026-04-30-001.md
│ └── bug-2026-04-30-002.md
├── config/
│ ├── subagents.json
│ └── skills-cache.json
└── memory/
├── summaries/
│ └── thread-abc123-summary.md
└── facts/
└── global-facts.json
JSON files: Machine-readable structured data
{
"thread_id": "abc123",
"created_at": "2026-04-30T10:00:00Z",
"updated_at": "2026-04-30T11:00:00Z",
"messages": [
{"role": "user", "content": "Hello"},
{"role": "assistant", "content": "Hi there!"}
],
"metadata": {
"total_tokens": 150,
"model": "claude-sonnet-4"
}
}Markdown files: Human-readable with YAML frontmatter
---
type: conversation_summary
thread_id: abc123
timestamp: 2026-04-30T11:00:00Z
tokens: 500
---
## Summary
User is building a PHP library for AI assistants. Main concerns are:
- Context condensation for sub-agents
- MCP integration
- Auto-learning capabilities
## Key Facts
- Using Neuron AI as base framework
- Wants zero additional dependencies
- PHP 8.2 minimum
- Package name: hacklab/ai-assistantExtends Neuron's AbstractChatHistory with multi-level memory:
class HierarchicalChatHistory extends AbstractChatHistory
{
public function __construct(
int $contextWindow = 150000,
protected int $summaryThreshold = 10000,
protected int $recentMessages = 5,
protected ?AIProviderInterface $summarizationProvider = null,
) {
parent::__construct($contextWindow);
}
/**
* Get messages with hierarchy applied.
*
* Returns:
* 1. System summary message (if available)
* 2. Key facts message (if available)
* 3. Recent messages (full content)
*/
public function getMessages(): array;
/**
* Trigger summarization of old messages.
*/
public function summarize(): void;
/**
* Extract and store key facts.
*/
public function extractFacts(): void;
}- Summary: Condensed text of older conversation (replaced every N messages)
- Key Facts: Structured facts extracted from conversation (user preferences, decisions, etc.)
- Recent Messages: Last N messages kept verbatim
class Assistant extends Agent
{
protected function chatHistory(): ChatHistoryInterface
{
return new HierarchicalChatHistory(
contextWindow: $this->config->contextWindow,
summarizationProvider: $this->config->summarizationProvider,
);
}
}Users can implement their own storage:
use HackLab\AIAssistant\Persistence\StorageInterface;
class RedisStorage implements StorageInterface
{
public function __construct(protected \Redis $redis) {}
public function save(string $key, array $data): void
{
$this->redis->set($key, json_encode($data));
}
public function load(string $key): ?array
{
$data = $this->redis->get($key);
return $data ? json_decode($data, true) : null;
}
// ... other methods
}use HackLab\AIAssistant\AssistantConfig;
$config = new AssistantConfig(
storage: new FileStorage(__DIR__ . '/storage'),
// Or custom:
// storage: new RedisStorage($redis),
);You can assign different storage backends for each domain. When omitted, the main storage is used as fallback.
$config = new AssistantConfig(
provider: $provider,
storage: new FileStorage('/data/default'),
conversationStorage: new RedisStorage($redis), // conversations in Redis
learningStorage: new FileStorage('/data/learning'), // learning data on disk
userMemoryStorage: new DatabaseStorage($pdo), // user memories in database
);| Parameter | Domain | Used by |
|---|---|---|
conversationStorage |
Conversations (threads) | ConversationStore |
learningStorage |
Patterns, bugs, entries | KnowledgeBase |
userMemoryStorage |
Per-user memories | UserMemoryStore |
Neuron provides:
InMemoryChatHistoryFileChatHistorySQLChatHistoryEloquentChatHistory
Our HierarchicalChatHistory can wrap any of these:
$hierarchical = new HierarchicalChatHistory(
contextWindow: 150000,
underlying: new SQLChatHistory(
thread_id: 'thread-123',
pdo: $pdo,
),
);