- name
- tencentdb-agent-memory
- description
- Local-first long-term memory for AI agents with symbolic short-term memory and layered long-term recall via a 4-tier progressive pipeline
- triggers
- ["add agent memory to my project","implement long-term memory for my AI agent","set up TencentDB Agent Memory","configure agent memory with symbolic compression","enable memory layering for my agent","integrate agent memory with OpenClaw","reduce token usage with memory offloading","add persona and scenario memory"]
# tencentdb-agent-memory
> Skill by [ara.so](https://ara.so) — AI Agent Skills collection.
## What It Does
TencentDB Agent Memory provides **fully local** long-term memory for AI agents through a 4-tier progressive pipeline:
- **L0 Conversation**: Raw dialogue logs
- **L1 Atom**: Atomic facts extracted from conversations
- **L2 Scenario**: Scene blocks aggregated from atoms
- **L3 Persona**: User profile distilled from scenarios
It combines **symbolic short-term memory** (Mermaid canvas offloading) with **layered long-term memory** (persona/scenario pyramid) to:
- Cut token usage by up to **61.38%** (WideSearch benchmark)
- Improve task success by up to **51.52%** relative (WideSearch)
- Boost PersonaMem accuracy from **48%** to **76%**
**Zero external API dependencies** — runs entirely on local SQLite + sqlite-vec.
## Installation
### For OpenClaw
```bash
# Install the plugin
openclaw plugins install @tencentdb-agent-memory/memory-tencentdb
# Restart the gateway
openclaw gateway restart
```
### For Hermes (Docker)
```bash
# Build the image
docker build -f Dockerfile.hermes -t hermes-memory .
# Run with your LLM credentials
docker run -d \
--name hermes-memory \
--restart unless-stopped \
-p 8420:8420 \
-e MODEL_API_KEY="${YOUR_API_KEY}" \
-e MODEL_BASE_URL="https://api.lkeap.cloud.tencent.com/v1" \
-e MODEL_NAME="deepseek-v3.2" \
-e MODEL_PROVIDER="custom" \
-v hermes_data:/opt/data \
hermes-memory
```
### As a Standalone Library
```bash
npm install @tencentdb-agent-memory/memory-tencentdb
```
```typescript
import { TencentDBMemory } from '@tencentdb-agent-memory/memory-tencentdb';
const memory = new TencentDBMemory({
storage: {
type: 'sqlite',
dbPath: './data/memory.db'
}
});
await memory.initialize();
```
## Configuration
### OpenClaw Zero-Config (Minimal)
```jsonc
// ~/.openclaw/openclaw.json
{
"memory-tencentdb": {
"enabled": true
}
}
```
### Enable Short-Term Compression
```jsonc
{
"memory-tencentdb": {
"enabled": true,
"config": {
"offload": {
"enabled": true
}
}
},
"plugins": {
"slots": {
"contextEngine": "openclaw-context-offload"
}
}
}
```
Then apply the runtime patch:
```bash
bash scripts/openclaw-after-tool-call-messages.patch.sh
```
### Full Configuration Schema
```jsonc
{
"memory-tencentdb": {
"enabled": true,
"config": {
// Storage backend
"storage": {
"type": "sqlite", // or "postgres"
"dbPath": "./data/memory.db",
// For PostgreSQL:
// "host": "localhost",
// "port": 5432,
// "database": "agent_memory",
// "user": "postgres",
// "password": "${POSTGRES_PASSWORD}"
},
// LLM for memory extraction
"llm": {
"provider": "custom",
"apiKey": "${LLM_API_KEY}",
"baseURL": "https://api.lkeap.cloud.tencent.com/v1",
"model": "deepseek-v3.2"
},
// Embedding model (local by default)
"embedding": {
"provider": "local", // or "openai"
"model": "Xenova/all-MiniLM-L6-v2",
"dimension": 384
},
// Memory extraction control
"memory": {
"autoExtract": true,
"extractInterval": 10, // Extract every 10 messages
"personaUpdateThreshold": 50 // Update persona after 50 atoms
},
// Short-term offload
"offload": {
"enabled": true,
"maxContextTokens": 100000,
"compressionRatio": 0.3
},
// Recall settings
"recall": {
"topK": 5,
"similarityThreshold": 0.6,
"includePersona": true,
"includeScenarios": true
}
}
}
}
```
## Core Concepts
### Memory Layers
```
L3 Persona (user profile, preferences)
↑ aggregates from
L2 Scenario (scene blocks, common patterns)
↑ extracts from
L1 Atom (atomic facts)
↑ distills from
L0 Conversation (raw dialogue)
```
### Symbolic Memory (Mermaid Canvas)
Long tool outputs are:
1. **Offloaded** to external files (`refs/*.md`)
2. **Compressed** into Mermaid graph nodes with `node_id`
3. **Recalled** via `node_id` lookup when needed
```mermaid
graph LR
A["Search Results (50k tokens)"] -->|offload| B[("refs/search-001.md")]
A -->|symbolize| C["node_1234<br/>type: search<br/>summary: 3 key findings"]
C -.recall via node_id.-> B
```
## Usage Examples
### Store a Conversation
```typescript
import { TencentDBMemory } from '@tencentdb-agent-memory/memory-tencentdb';
const memory = new TencentDBMemory({
storage: { type: 'sqlite', dbPath: './memory.db' }
});
await memory.initialize();
// Store conversation turn
await memory.storeConversation({
sessionId: 'session-123',
userId: 'user-456',
messages: [
{ role: 'user', content: 'I prefer tabs over spaces' },
{ role: 'assistant', content: 'Got it, I'll use tabs in code.' }
],
metadata: { topic: 'coding-preferences' }
});
```
### Extract Atoms and Update Persona
```typescript
// Auto-extract runs every N messages (configurable)
// Or trigger manually:
await memory.extractAtoms('session-123');
// Check extracted atoms
const atoms = await memory.getAtoms('user-456', { limit: 10 });
console.log(atoms);
// [
// { id: 'atom-1', content: 'User prefers tabs over spaces', category: 'preference' }
// ]
// Persona updates automatically after threshold
const persona = await memory.getPersona('user-456');
console.log(persona);
// {
// userId: 'user-456',
// profile: 'Prefers tabs, uses TypeScript, likes functional patterns',
// preferences: { coding_style: 'tabs', language: 'TypeScript' }
// }
```
### Recall Relevant Memories
```typescript
// Before agent turn, recall relevant context
const recalled = await memory.recall({
userId: 'user-456',
query: 'how should I format this code?',
topK: 5,
includePersona: true,
includeScenarios: true
});
console.log(recalled);
// {
// persona: { profile: '...', preferences: {...} },
// scenarios: [{ title: 'Code formatting', steps: [...] }],
// atoms: [{ content: 'User prefers tabs', score: 0.89 }]
// }
```
### Offload Tool Output (Short-Term Memory)
```typescript
// Large tool output
const toolOutput = {
tool: 'search',
result: '...(50,000 tokens of search results)...'
};
// Offload and get symbolic reference
const offloaded = await memory.offloadContext({
sessionId: 'session-123',
content: toolOutput.result,
metadata: { tool: 'search', timestamp: Date.now() }
});
console.log(offloaded);
// {
// nodeId: 'node-1234',
// mermaidNode: 'node-1234["🔍 Search: 3 findings"]',
// refPath: 'refs/search-001.md',
// summary: 'Found 3 relevant papers on neural memory'
// }
// Later, recover if needed
const recovered = await memory.recoverContext('node-1234');
console.log(recovered.content); // Full original text
```
### Query Scenarios
```typescript
// Get common solution patterns
const scenarios = await memory.getScenarios('user-456', {
topic: 'debugging',
limit: 3
});
console.log(scenarios);
// [
// {
// id: 'scenario-1',
// title: 'TypeScript type errors',
// pattern: 'Check tsconfig, verify imports, run tsc --noEmit',
// frequency: 12
// }
// ]
```
## Common Patterns
### Agent Loop with Memory
```typescript
async function agentLoop(userId: string, userMessage: string) {
const sessionId = `session-${Date.now()}`;
// 1. Recall relevant context
const memory = await memorySystem.recall({
userId,
query: userMessage,
topK: 5
});
// 2. Build prompt with persona + recalled facts
const systemPrompt = `
User Profile: ${memory.persona?.profile || 'No profile yet'}
Relevant memories:
${memory.atoms.map(a => `- ${a.content}`).join('\n')}
Recent scenarios:
${memory.scenarios.map(s => `- ${s.title}: ${s.pattern}`).join('\n')}
`.trim();
// 3. Call LLM
const response = await llm.chat({
messages: [
{ role: 'system', content: systemPrompt },
{ role: 'user', content: userMessage }
]
});
// 4. Store conversation
await memorySystem.storeConversation({
sessionId,
userId,
messages: [
{ role: 'user', content: userMessage },
{ role: 'assistant', content: response }
]
});
return response;
}
```
### Periodic Persona Refresh
```typescript
// Run nightly to update user personas
async function refreshPersonas() {
const users = await memory.getAllUsers();
for (const userId of users) {
const atomCount = await memory.getAtomCount(userId);
if (atomCount >= 50) { // Threshold
await memory.updatePersona(userId);
console.log(`Updated persona for ${userId}`);
}
}
}
// Schedule with cron
// 0 2 * * * node refresh-personas.js
```
### Offload Long Tool Outputs
```typescript
async function handleToolCall(tool: string, args: any) {
const result = await executeTool(tool, args);
// If result is large, offload
const tokenCount = estimateTokens(result);
if (tokenCount > 10000) {
const offloaded = await memory.offloadContext({
sessionId: currentSession,
content: result,
metadata: { tool, args }
});
return {
summary: offloaded.summary,
nodeId: offloaded.nodeId,
// Agent sees only Mermaid node in context
mermaid: offloaded.mermaidNode
};
}
return result; // Small results stay inline
}
```
## Troubleshooting
### Memory not extracting
**Check extraction interval:**
```jsonc
{
"memory": {
"autoExtract": true,
"extractInterval": 10 // Extracts every 10 messages
}
}
```
**Manually trigger:**
```typescript
await memory.extractAtoms('session-id');
```
### Persona not updating
**Check threshold:**
```jsonc
{
"memory": {
"personaUpdateThreshold": 50 // Needs 50+ atoms
Auf GitHub ansehen