MemMesh — persistent, self-improving memory for AI agents. Get started →
Core ConceptsBehavior Discovery

Behavior discovery

Most systems detect a fixed menu of behaviors. MemMesh discovers behaviors nobody defined — it clusters subjects by how they actually behave and surfaces the dense, cohesive groups as candidate behaviors.

const { behaviors } = await tf.behaviors.discover();
 
for (const b of behaviors) {
  console.log(
    `${b.label} — ${(b.prevalence * 100).toFixed(0)}% of subjects, ` +
    `stability ${b.stability.toFixed(2)}, ${b.size} members`,
  );
}

What a discovered behavior is

Each behavior is a cluster of like-behaving subjects with the statistics that justify treating it as real:

FieldMeaning
labelA human description (“high-value frequent reorderers”).
prevalenceFraction of the analyzed cohort in this cluster — how common.
stabilityCohesion (mean intra-cluster similarity) — how tightly they behave alike.
memberSubjectsWho exhibits it (medoid first) — provenance.
exemplarEvidenceThe signals that define it (“pattern: recurring_event”).

Results are sorted most-common-and-cohesive first.

Feeding RFM (recency · frequency · monetary)

RFM reads structured fields off each event’s metadata — a value written only into free-text content is not parsed. Set these when you observe/create:

Monetary. The order total is read from the first present of these keys, then falls back to summing a lineItems array (amount|price|total × quantity):

value · total · grandTotal · orderTotal · totalAmount · amount · subtotal · price · revenue

Numbers and numeric strings are accepted — 42.5, "42.50", "$42.50", "1,234.56" all work (US-format decimals; a leading currency symbol and thousands separators are stripped). If none of these keys carries a number, the subject is treated as non-transactional and monetaryScore is 0.

{ "subject": { "kind": "contact", "externalId": "sarah" },
  "content": "Order placed",
  "metadata": { "orderTotal": "42.50", "occurredAt": "2026-05-01T09:00:00Z" } }

Frequency is scored against the subject’s own first→last event span, not a fixed window, and bucketed by cadence: weekly-or-more → 5, biweekly → 4, monthly → 3, quarterly → 2, rarer → 1. This is why backfills must set event time (occurredAt / validFrom): without it every imported row lands at import time and the span collapses.

Recency is days since the most recent event.

It abstains, too

Discovery only promotes clusters the statistics vouch for. A weak or loose cluster is treated as noise, not a behavior.

⚠️

An empty result means the engine abstained — there isn’t enough signal to assert a behavior yet. It does not mean “this cohort has no behaviors.”

Neuro-symbolic naming

Discovery is the symbolic half of a neuro-symbolic loop: the statistics find and verify clusters; an LLM then names them. Because the model only ever labels a cluster the data already vouched for, it can name a behavior but never invent one.

From discovery to prediction

Discovery is the exploratory, project-wide view — what cohorts exist? The per-subject behavior_pattern memories that feed prediction are produced by pattern mining (/lattice/patterns/extract). The two share the same statistical core, closing the loop: mine → verify → predict, with discovery surfacing the emergent groups.

See the Behavior discovery API for the full request and response shape, and the TypeScript SDK for the behaviors.discover surface.