Memory
Two coexisting memory systems for the agent. Pick the one that matches your shape:
| System | When | Storage |
|---|---|---|
| Memory v1 — 3-tier hybrid (core / episodic / semantic) | You want the agent to remember user profile + past sessions + retrievable facts automatically across runs | localStorage by default; semantic tier is host-wired |
| DrawerMemory — verbatim drawer store + BM25 | You want a queryable note store the host explicitly writes into | memory (default) / localStorage / indexeddb |
Both opt-in. Default new DotDotDuck({}) has no memory active.
Memory v1
Three tiers, each independently opt-in.
import { DotDotDuck } from '@perhapxin/dddk';
const dddk = new DotDotDuck({
memory: {
core: {
schema: { language: 'string', tone: 'string', recent_pages: 'array' },
storage: 'localStorage',
maxBytes: 2048,
},
episodic: {
enabled: true,
maxEntries: 50,
replayTopK: 3,
relevance: 'recency', // or 'keyword'
},
semantic: {
provider: myVectorProvider, // host wires
writeMode: 'explicit',
},
privacy: {
requireConsent: true,
retentionDays: 90,
excludePatterns: [/myCustomPattern/],
},
},
});
Tier 1 — core
Structured user profile. Always-in-context. Hard byte cap. Schema enforces field shapes:
dddk.memory.core?.set('language', 'zh-TW');
dddk.memory.core?.set('tone', 'concise');
dddk.memory.core?.get('language'); // 'zh-TW'
Renders as # About this user system-prompt block on every run.
Tier 2 — episodic
Ring buffer of past session summaries:
// Host calls when a session ends — usually after agent_run_completed
dddk.memory.endSession({
summary: 'Helped user navigate commercial tier comparison',
taskSummary: 'Explain commercial license tiers',
turnCount: 4,
});
Renders as # Past relevant sessions system-prompt block. relevance: 'recency' takes last N; 'keyword' does token overlap against the new query.
Tier 3 — semantic
Host implements MemoryProvider:
interface MemoryProvider {
remember(entry: Omit<SemanticMemoryEntry, 'id' | 'timestamp'>): Promise<string>;
recall(query: string, opts?: { topK?: number; tags?: string[] }): Promise<SemanticMemoryEntry[]>;
forget(id: string): Promise<void>;
list?(opts?: { tags?: string[]; limit?: number }): Promise<SemanticMemoryEntry[]>;
}
dddk calls recall(userQuery, { topK: 5 }) on every run start. Returned entries render as # Recalled facts system-prompt block.
dddk does NOT embed, does NOT persist — the provider owns infra.
Privacy
| Field | Effect |
|---|---|
requireConsent: true |
First-write triggers a host consent hook |
retentionDays |
Episodic entries past this age are dropped on read |
excludePatterns |
Regex set; matching text is dropped before write. Defaults block credit-card / API-key / private-key shapes |
Auto-injection
When any tier is attached, dddk.startAgent(task) automatically runs memory.buildContext(task) and prepends the result to the task. No manual wiring needed.
DrawerMemory
The verbatim note store (renamed from Memory in 0.1.0).
import { DrawerMemory, createDrawerMemory } from '@perhapxin/dddk/agent';
const memory = new DrawerMemory({
storage: 'memory', // 'memory' (default) / 'localStorage' / 'indexeddb'
customerId: 'cust_4823',
});
memory.addDrawer({ text: 'user said: my item is broken', ts: Date.now() });
memory.addDrawer({ wing: 'orders', room: 'ORD-1002', text: 'order ORD-1002 → $990 charged on 2026-05-20', ts: Date.now() });
const recent = await memory.recent({ limit: 20 });
const hits = await memory.search('broken', { topK: 5 });
wing / room scope
Drawers tag with optional wing (major category) + room (sub-category). Search can scope:
await memory.search('refund', { scope: { wing: 'orders' } });
await memory.wing('orders').room('ORD-1002').search('paid');
Not a webagent tool by design
DrawerMemory is for host code — a skill, a sidebar, a debug panel that wants to query session state. It is NOT exposed as a tool the LLM can call. Long-term facts that the LLM should see go via Memory v1 (semantic tier).
When to use which
- "Agent should know user prefers Chinese" → Memory v1 core
- "Agent should remember what we talked about last week" → Memory v1 episodic + semantic if you have vector recall
- "I want to log customer service notes the agent can search later" → DrawerMemory (host code drives the writes)
- "I want a typed key-value session state for skills" → use SDK preferences, not memory
Coexistence
Both can be active in the same DotDotDuck instance — dddk.memory is the 3-tier instance, new DrawerMemory(...) lives wherever the host wants it.
Backwards compatibility
The old Memory / createMemory exports still resolve as aliases for DrawerMemory / createDrawerMemory. They will log a deprecation warning in 0.2.