Memory
Block-based memory primitives for prompts, agents, stores, and observability.
import {
memory,
memoryBlock,
recentMessages,
workingState,
episodes,
facts,
procedures,
reflections,
} from '@crux/core/memory'memory(config)
Composes memory blocks into a prompt-usable memory object.
const mem = memory({
id: 'assistant',
store,
namespace: ({ input }) => `user:${input.userId}`,
blocks: [recentMessages({ id: 'recent' }), facts({ id: 'facts' })],
processing: { mode: 'deferred' },
})memory() returns an object with _tag: 'Memory', asContext(), asTools(), captureTurn(), captureToolEvent(), flush(), and proposals.
Prompts can use the memory object directly:
prompt({
use: [mem],
// ...
})memoryBlock(config)
Creates a custom memory block.
const block = memoryBlock({
id: 'custom',
kind: 'custom',
priority: 50,
render: async (ctx) => 'Rendered memory',
captureTurn: async (turn, ctx) => {},
tools: (ctx) => ({}),
})A block can implement render, tools, turn capture, tool-event capture, flush, and proposal approval independently.
Built-In Blocks
recentMessages({ id, maxMessages?, priority? }) stores a bounded rolling message window.
Methods: addTurn(turn, options), list(options), clear(options).
workingState({ id, schema, priority? }) stores one Zod-validated value.
Methods: get(options), set(value, options), patch(value, options), clear(options).
episodes({ id, embed?, priority?, retention? }) stores append-only events and recalls them with a dense embedding when available. retention (e.g. '90d') is a descriptive eviction-window policy surfaced on every read/write event's metadata.
Methods: record(entry, options), recall(query, options), list(options), delete(key, options), evict(key, options).
evict(key, options) is delete for retention sweeps: it removes the entry and emits an evict write op carrying GC telemetry (lastGcAt, lastGcEvicted) so evictions are attributable in devtools rather than silent. options extends the runtime options with evictedCount? and gcAt?.
facts(config) stores extracted facts. procedures(config) stores extracted operating memory. Both support proposed writes by default.
Methods: add(entry, options), find(query, options), list(options), delete(key, options), render(options).
reflections(config) stores generated reflections on supplied memory text. It is a small wrapper for reflection workflows and can be expanded by custom blocks when products need richer behavior.
Runtime Options
Direct block methods accept:
type MemoryRuntimeOptions = {
store: CruxStore
namespace: string
memoryId?: string
traceId?: string
promptId?: string
}Use the same memoryId as the composed memory() object when direct block calls should share storage keys with prompt-bound memory.
Proposals
memory().proposals manages proposed long-term memory writes:
const pending = await mem.proposals.list({
namespace: 'user:123',
blockId: 'facts',
status: 'pending',
})
await mem.proposals.approve(pending[0].id)
await mem.proposals.edit(pending[1].id, { content: 'Edited fact' })
await mem.proposals.reject(pending[2].id, { reason: 'Not true' })facts() and procedures() default to write: { mode: 'propose' }. Use write: { mode: 'auto' } when the product has a safe automatic write path.
Observability
Block memory emits memory.read and memory.write spans with:
{
memoryType: 'block',
memoryId: string,
blockId: string,
blockKind: 'recent' | 'working' | 'episodes' | 'facts' | 'procedures' | 'reflections' | 'custom',
namespaceHash: string,
}Reads that return entries also attach memory.recall artifacts with returned and blocks[] summaries (blockKind, key, preview, and optional score). Empty reads emit the read span but do not attach memory.recall, which keeps devtools from showing empty recalled-block cards. Writes with inspectable before/after state attach memory.diff artifacts with before, after, and added/removed/updated block summaries. Write events may also include writeMode and proposalStatus.