FabricFabricHarness
Building Agents

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.memorysession entries (fh export-audit)
PurposeRecall — facts the agent should rememberAudit — what the agent did
PersistenceUser-controlled key/valueAppend-only log
Visibility to modelOnly when the agent reads + injectsThrough compaction / replay
SizeBounded per keyGrows with each action
Typical readsTargeted: 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

BackendWhereUse when
inMemorySessionMemory()@fabric-harness/sdkTests, single-process pilots, ephemeral agents.
postgresSessionMemory({ client })@fabric-harness/nodeProduction. 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.

See also