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:
| Field | Meaning |
|---|---|
label | A human description (“high-value frequent reorderers”). |
prevalence | Fraction of the analyzed cohort in this cluster — how common. |
stability | Cohesion (mean intra-cluster similarity) — how tightly they behave alike. |
memberSubjects | Who exhibits it (medoid first) — provenance. |
exemplarEvidence | The 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.