Tutorial 3: Memory System
Implement persistent storage with itemTypes. Store data that persists between conversations.
3.1 Understanding Memory
OpenKBS provides a built-in key-value store that:
- Persists data between conversations
- Automatically encrypts sensitive fields
- Injects priority items into LLM context
- Supports TTL (expiration)
3.2 Configure itemTypes
Edit app/settings.json:
{
"model": "gemini-2.5-pro-preview-03-25",
"inputTools": ["speechToText"],
"itemTypes": {
"memory": {
"attributes": [
{ "attrName": "itemId", "attrType": "itemId", "encrypted": false },
{ "attrName": "body", "attrType": "body", "encrypted": true }
]
},
"agent": {
"attributes": [
{ "attrName": "itemId", "attrType": "itemId", "encrypted": false },
{ "attrName": "body", "attrType": "body", "encrypted": true }
]
}
},
"options": {
"priorityItems": [
{ "prefix": "memory", "limit": 100 },
{ "prefix": "agent", "limit": 20 }
]
}
}Key settings:
itemTypes- Define storage collections (memory, agent)encrypted: true- Body is automatically encrypted at restpriorityItems- Items with these prefixes are always in LLM contextlimit- Max items per prefix (prevents context overflow)
3.3 Priority Items
Items matching priorityItems prefixes are automatically injected into the LLM context before each request:
"priorityItems": [
{ "prefix": "memory", "limit": 100 },
{ "prefix": "agent", "limit": 20 }
]This means:
- All
memory_*items (up to 100) are visible to the LLM - All
agent_*items (up to 20) are visible to the LLM - No need to manually fetch them - they're in context automatically
3.4 Memory Commands
Add to src/Events/actions.js:
Helper Functions
// Upsert pattern - update or create if not exists
async function upsertItem(itemType, itemId, body) {
try {
await openkbs.updateItem({ itemType, itemId, body });
} catch (e) {
await openkbs.createItem({ itemType, itemId, body });
}
}
// Set memory with optional expiration
async function setMemoryValue(itemId, value, expirationInMinutes = null) {
if (!itemId.startsWith('memory_')) {
itemId = `memory_${itemId}`;
}
const body = {
value,
updatedAt: new Date().toISOString()
};
if (expirationInMinutes != null) {
body.exp = new Date(Date.now() + expirationInMinutes * 60 * 1000).toISOString();
}
await upsertItem('memory', itemId, body);
return { success: true, itemId };
}setMemory Command
[/<setMemory>([\s\S]*?)<\/setMemory>/s, async (match) => {
try {
const data = JSON.parse(match[1].trim());
let itemId = data.itemId;
// Ensure prefix
if (!itemId.startsWith('memory_')) {
itemId = `memory_${itemId}`;
}
await setMemoryValue(itemId, data.value, data.expirationInMinutes);
return {
type: "MEMORY_SAVED",
itemId,
expires: data.expirationInMinutes
? `in ${data.expirationInMinutes} minutes`
: 'never',
...meta,
_meta_actions: ["REQUEST_CHAT_MODEL"]
};
} catch (e) {
return { type: "ERROR", error: e.message, ...meta };
}
}],deleteItem Command
[/<deleteItem>([\s\S]*?)<\/deleteItem>/s, async (match) => {
try {
const data = JSON.parse(match[1].trim());
await openkbs.deleteItem(data.itemId);
return {
type: "ITEM_DELETED",
itemId: data.itemId,
...meta,
_meta_actions: ["REQUEST_CHAT_MODEL"]
};
} catch (e) {
return { type: "ERROR", error: e.message, ...meta };
}
}],cleanupMemory Command
[/<cleanupMemory\s*\/>/s, async () => {
try {
const result = await openkbs.fetchItems({
beginsWith: 'memory_',
limit: 100
});
if (!result?.items) return { cleaned: 0, ...meta };
let cleaned = 0;
const now = new Date();
for (const item of result.items) {
if (item.item?.body?.exp) {
const expDate = new Date(item.item.body.exp);
if (expDate < now) {
await openkbs.deleteItem(item.meta.itemId);
cleaned++;
}
}
}
return {
type: "CLEANUP_COMPLETE",
cleaned,
...meta,
_meta_actions: ["REQUEST_CHAT_MODEL"]
};
} catch (e) {
return { type: "ERROR", error: e.message, ...meta };
}
}],3.5 Define in Instructions
Add to app/instructions.txt:
## Memory Commands
<setMemory>
{
"itemId": "memory_user_name",
"value": "John",
"expirationInMinutes": null
}
</setMemory>
Description: Save data to memory. Use null for permanent storage.
<deleteItem>
{
"itemId": "memory_old_data"
}
</deleteItem>
Description: Delete a memory item.
<cleanupMemory/>
Description: Remove all expired memory items.
## Memory Best Practices
1. Use descriptive IDs: memory_user_preferences, memory_project_notes
2. Keep under 100 items total
3. Set expiration for temporary data
4. Consolidate related data into single items3.6 SDK Methods
Create/Update Item
// Create new item
await openkbs.createItem({
itemType: 'memory',
itemId: 'memory_user_name',
body: { value: 'John', updatedAt: new Date().toISOString() }
});
// Update existing item
await openkbs.updateItem({
itemType: 'memory',
itemId: 'memory_user_name',
body: { value: 'Jane', updatedAt: new Date().toISOString() }
});Get Item
const result = await openkbs.getItem('memory_user_name');
console.log(result.item.body.value); // 'Jane'Fetch Multiple Items
const items = await openkbs.fetchItems({
itemType: 'memory',
beginsWith: 'memory_',
limit: 100
});
for (const { item, meta } of items.items) {
console.log(meta.itemId, item.body.value);
}Delete Item
await openkbs.deleteItem('memory_user_name');3.7 Deploy and Test
openkbs pushTest:
- "Remember my name is John" → Should save to memory
- "What's my name?" → Should recall from memory
- "Forget my name" → Should delete the item
Summary
itemTypesin settings.json define storage collectionspriorityItemsauto-inject items into LLM context- Upsert pattern handles create vs update
- TTL support via
expfield - Limit items to ~100 per prefix to avoid context overflow
Next
Tutorial 4: Scheduled Tasks - One-time reminders and cronjobs.