RODEO

Audiences

Target profiles with context, rhythm, and contact bounds

An audience is who a scene targets — an individual, a role type, or a cohort.

Types

TypeDescriptionExample
individualSpecific person"Jordan, mobile butcher in TX Hill Country"
roleType of person"Mobile butchers in central Texas"
cohortShared characteristics"Lapsed subscribers from Q3"

Context

Freeform JSON describing the target:

context: {
  name: "Jordan",
  role: "mobile butcher",
  region: "TX-Hill-Country",
  timezone: "America/Chicago",
  notes: "Prefers early morning. Works weekends.",
}

Informs beat composition and timing; not system-enforced.

Rhythm

rhythm: {
  preferredTimes: ["08:00-10:00", "17:00-19:00"],
  preferredDays: ["mon", "tue", "wed", "thu", "fri"],
  timezone: "America/Chicago",
}

Bounds

Hard limits enforced by the membrane. Violations produce bounds_blocked status.

bounds: {
  maxBeatsPerDay: 3,
  maxBeatsPerWeek: 8,
  cooldownMinutes: 60,
}

State

FieldDescription
lastContactedLast beat fired
beatsTodayBeats fired today
beatsThisWeekBeats fired this week
cooldownUntilEarliest next beat
declinedExplicit decline flag
signalsAccumulated sensor signals

Auto-initialized on creation, updated as beats fire.

Enforcement

The membrane checks before every beat:

  1. declined true? Block beat, terminate scene
  2. cooldownUntil in future? Block beat
  3. beatsToday >= maxBeatsPerDay? Block beat
  4. beatsThisWeek >= maxBeatsPerWeek? Block beat

Blocked beats trigger the scene's onBoundsExceeded handler.

Usage

const audience = await rodeo.audiences.create({
  type: "individual",
  identity: { externalId: "butcher-042" },
  context: { name: "Jordan", role: "mobile butcher", region: "TX-Hill-Country" },
  bounds: { maxBeatsPerDay: 3, maxBeatsPerWeek: 8, cooldownMinutes: 60 },
  rhythm: { preferredTimes: ["08:00-10:00"], timezone: "America/Chicago" },
});