Session memory
Persistent key/value recall across sessions, scoped by tenant. Distinct from session entries (audit log).
session.memory lets agents remember facts across sessions — borrower preferences, prior outcomes, learned task history. It's a typed key/value store backed by SessionMemory, scoped automatically by the session's tenantId.
Memory vs. entries
session.memory | session entries (fh export-audit) | |
|---|---|---|
| Purpose | Recall — facts the agent should remember | Audit — what the agent did |
| Persistence | User-controlled key/value | Append-only log |
| Visibility to model | Only when the agent reads + injects | Through compaction / replay |
| Size | Bounded per key | Grows with each action |
| Typical reads | Targeted: memory.get('borrower:preferences') | Full session view: fh inspect, fh logs |
Memory writes do NOT land in the session log. Audit is unaffected.
API
const fabric = await init({
tenantId: 'tenant-acme',
memory: postgresSessionMemory({ client: pgClient }), // or inMemorySessionMemory()
});
const session = await fabric.session();
// Get / set typed values
await session.memory.set({
key: 'borrower:42:preferences',
value: { contactChannel: 'sms', timezone: 'America/Phoenix' },
ttlSeconds: 60 * 60 * 24 * 30, // optional 30-day TTL
});
const entry = await session.memory.get<{ contactChannel: string }>('borrower:42:preferences');
// { key, value, tenantId, updatedAt, expiresAt?, metadata? }
// List with prefix + recency filters
const recent = await session.memory.list({ keyPrefix: 'borrower:42:*', limit: 10 });
await session.memory.delete('borrower:42:preferences');session.memory auto-scopes every operation to the session's tenantId. Two tenants writing to the same key get isolated values.
Backends
| Backend | Where | Use when |
|---|---|---|
inMemorySessionMemory() | @fabric-harness/sdk | Tests, single-process pilots, ephemeral agents. |
postgresSessionMemory({ client }) | @fabric-harness/node | Production. Survives restarts, shares across a fleet. |
Postgres schema:
CREATE TABLE fabric_harness_session_memory (
tenant_id TEXT NOT NULL DEFAULT '',
key TEXT NOT NULL,
value JSONB NOT NULL,
metadata JSONB,
expires_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
PRIMARY KEY (tenant_id, key)
);Created automatically on construction. Pass initialize: false if you manage migrations separately.
Patterns
Borrower preferences (long-term). Set on first contact; read on subsequent sessions to skip re-asking.
const prefs = await session.memory.get<{ channel: 'sms' | 'email' }>('borrower:42:contact');
if (!prefs) {
await session.prompt('Ask the borrower how they prefer to be contacted.');
await session.memory.set({ key: 'borrower:42:contact', value: { channel: 'sms' } });
}Task history (medium-term). Use TTL so old context doesn't accumulate.
await session.memory.set({
key: `task:${taskId}:summary`,
value: { result, durationMs },
ttlSeconds: 60 * 60 * 24 * 7, // 7 days
});Cross-tenant guards. Memory operations scope by tenantId automatically. The SDK never mixes tenants — set({ key: 'k' }) from tenant-a and tenant-b produce isolated rows.
Reset / cleanup
Memory has no built-in expiry sweeper — TTL'd rows are filtered out on read. For high-volume deployments, run a periodic DELETE FROM fabric_harness_session_memory WHERE expires_at < NOW() from your scheduler. The expires_at column is indexed for that purpose.