Skip to main content

Computed traits

A computed trait manufactures a new fact and stamps it onto a contact. You author a predicate over contact facts — LTV, last seen, lifecycle stage, opt-in flags — and every contact that matches gets the rule’s output_tag applied to their profile. That tag then behaves like any other attribute: segmentable, personalizable, exportable. The CDP audiences guide covers the API in brief. This page covers the dedicated Audience → Computed traits screen in the dashboard, the rule DSL in full, and the day-to-day operations behind it.

Deterministic rules, not AI suggestions

The platform carries three ways to derive traits. Pick deliberately, because the behavior differs:
  • Computed traits (this page) — a deterministic JSON predicate you author. Evaluation is a pure function: the same facts and the same predicate produce the same verdict every time. There is no LLM call in the evaluation path, so running a rule costs nothing per-contact.
  • AI traits — an LLM fills a fixed set of score columns (churn, LTV, lifecycle, next-best-action). Useful when the signal can not be expressed as a comparison against recorded facts.
  • Tag autopilot — an LLM proposes open-ended tag chips from free form language. A discovery surface, not a targeting primitive.
Reach for AI-derived surfaces when you genuinely need model judgment across unstructured text. When the rule can be written as a predicate — “LTV over 500 and seen this month” — author it here deterministically: cheap to run, reproducible in previews, and auditable in reviews, because the whole rule is right there in JSON.

Permissions

The Audience → Computed traits screen is visible to owner, admin, and developer roles. A member with any other role (analyst, marketer, user) sees a gate message instead of the list. The same boundary exists on the API — list, evaluate, and preview calls accept owner/admin/developer/analyst/marketer, but create, update, delete, and run require owner/admin/developer. The screen-level gate is a courtesy so members without write access ever see an empty, non-functional list.

The screen’s five states, and why they matter

The page is an exemplar of covering every state a list can be in, which is what makes it usable after you’ve authored more than a handful of rules:
  • Loading — skeleton rows match the table density, so the layout does not jump once data lands.
  • Empty — a quiet inline message with a Create your first trait button — not an unbroken table.
  • Error — an inline alert with a Retry button bound to re-fetch; a transient failure does not strand you.
  • Permission gate — covered above.
  • No matches — the toolbar search filters by name, description, and output tag; when a search excludes everything, a filtered empty state offers to clear the query instead of implying you have no traits at all.
Two further details keep the list smooth at scale: rows paginate through a cumulative “Load more” control, so a large rule set never mounts hundreds of table rows, and a failed delete surfaces the server error as a toast — the row stays in place so you can retry.

Author a rule in the dashboard

Open Audience → Computed traits and press Create trait. The editor sheet has two halves. On the left you define the rule itself:
  • Name and optional Description — the WHY behind the rule for the next operator.
  • Output tag — lowercase kebab-case, 1–64 characters (for example high-value-active). This is the identifier stamped onto matching contacts, so it must be unique across your tenant.
  • Predicate — the rule’s JSON body. A starter predicate is pre-filled so you are editing a working baseline, not staring at a blank area. The DSL accepts nested all_of / any_of / not combinators over leaf comparisons; the editor shows the exact leaf shape inline.
  • Enabled — a disabled rule persists but no longer evaluates; tags it already stamped stay in place.
On the right, in edit mode, the Test against facts panel runs the saved rule against a contact fact bag you paste inline. The verdict (Matches / Does not match) comes back without touching any contact — use it to answer “would this customer match?” before you turn the rule on, or to iterate on the predicate against synthetic facts.

The DSL vocabulary

A predicate tree is made of leaf comparisons nested under combinators. Depth is capped at 8 levels and total leaves at 32, which keeps worst-case evaluation a known constant. A leaf looks like this:
Combine leaves with all_of (AND), any_of (OR), and not:
Two rules that keep the DSL safe:
  • Missing facts never match. A fact that is absent or the wrong type evaluates every leaf to false — except a deliberate exists / not_exists check. Absence cannot creep into your audience by accident.
  • Versioned DSL. The predicate shape is pinned to a DSL version. Saved rules validate against it at read time, so an old rule is never silently misread by an upgraded evaluator.
The platform supplies common contact facts — predicted_ltv, churn_risk_score, lifecycle_stage, engagement_score, last_seen_at, created_at, total_revenue, email_optin, sms_optin — and any custom field key is fair game; the vocabulary is open.

The API surface

Everything the screen does, the API does. Owner/admin/developer for writes; the read plus evaluate endpoints accept analyst and marketer as well.
Updates are partial — send only the fields you are changing, and every field is individually optional. Property edits are idempotent by nature: repeating the same payload leaves the rule in the same state. A duplicate output_tag comes back 409; a malformed predicate is 422 with one error message per issue; a predicate that exceeds the depth or leaf budget is 400 with the reason inline. The preview endpoint takes a flat fact bag in the body and returns the boolean verdict without writing anything:
The dashboard’s Test against facts panel uses this same endpoint, so whatever verdict you see in the UI is reproduced exactly by the API call.

How assignments stay in sync

Creating a rule does not stamp it immediately. Two paths move tags onto contacts:
  • A scheduled evaluator sweeps enabled rules periodically and reconciles each assignment set, adding the tag to new matches and removing it from contacts that no longer match — so membership tracks the current data, not the data at first run.
  • Run now (the row action in the dashboard, or POST .../:id/run) applies a rule on demand, with the same add-and-remove reconciliation but bounded per run. The response reports evaluated, matched, tag_added, tag_removed, and a capped flag that tells you the run hit its per-run ceiling for large tenants. When capped is true the response tells you some contacts were not reached — re-run to continue the sweep.
Deleting a rule stops future evaluation; tags already on contacts stay in place. Every create, update, delete, and run is recorded in the audit log, so a marketing team’s rule history is answerable to “who changed this predicate and when.”

Where the stamped tags are used

Once a rule materializes, its output_tag is just another attribute. Segments filter on it (tag equals high-value-active), journeys branch on it, exports include it, and personalization slots read it. That is the point of the whole surface: a deterministic way to upconvert your raw facts into one stable, addressable, budget-friendly label — and to do it with the same rigor as the segment definitions in the CDP audiences guide.