What this is: agents making small, logged decisions that change routing, cadence, and variants.
What this isn’t: “AI writes your marketing for you.”
If you do only one thing: build a cadence governor.
Most teams hear “AI Agents” and jump straight to copy.
That’s not the win.
The win is using an agent to answer questions like:
- Should we send anything right now?
- Which path should this user go down?
- What’s the single next action that matters?
- When should we stop automating and hand off?
This is how you build AI into lifecycle without turning your CRM into a science experiment.
The system (in one picture)
`text
Lifecycle moment
(entry event / trigger)
|
v
+--------------------+
| Fetch signals | Connected Content / Catalogs / event props / warehouse
| (latest context) |
+--------------------+
|
v
+--------------------+
| Write snapshot | context_signals (structured)
| to user profile |
+--------------------+
|
v
+--------------------+
| Agent decision | constrained JSON + 1-line reason
+--------------------+
|
v
+--------------------+
| Canvas adapts | routing + cadence + channel + variant
+--------------------+
|
v
+--------------------+
| Re-check on timer | 30m / 24h / 7d
+--------------------+
`
Why this works: you’re not building infinite segments. You’re standardizing decision points.
Where signals come from
The sources that usually matter most:
- Connected Content → call your internal endpoints
- Canvas entry properties / event properties → immediate behavioral context
- Braze Catalogs → eligibility + content mapping (great for variant selection)
- Warehouse snapshots → daily or near-real-time user state
If you can answer “what’s true right now?” you can build agent decisioning.
The snapshot (one object, not attribute soup)
Create one object attribute as your source of truth. Keep it boring and readable.
`json
{
"context_signals": {
"as_of": "2026-01-29T17:30:00Z",
"source": "connected_content:user_state",
"fetch_ok": true,
"lifecycle_stage": "onboarding",
"locale": "en-US",
"time_zone": "America/New_York",
"last_message_at": "2026-01-28T20:10:00Z",
"last_open_at": "2026-01-28T20:32:00Z",
"last_key_action_at": "2026-01-28T21:05:00Z",
"quiet_hours": false,
"freq_cap_ok": true,
"consent_ok": true,
"friction": {
"verification_pending": false,
"payment_error": false,
"missing_setup_step": "add_payment_method"
},
"readiness": {
"activation_score": 42,
"fatigue_score": 18,
"support_risk": "low"
},
"eligibility": {
"can_send_sms": false,
"can_send_push": true,
"can_show_in_app": true
}
}
}
`
Rule: signals are for decisioning. Copy comes later.
What agents should control (and what they should NOT)
Agents SHOULD control
- Routing: which path the user enters next
- Cadence: how many touches and how quickly
- Channel choice: email vs push vs in-app vs wait
- Variant selection: pick from pre-built blocks
- Escalation: when to stop automating
Agents should NOT control (without guardrails)
- Inventing offers
- Making product promises
- Changing legal/compliance language
- Freestyling segmentation logic without audit trails
The agent contract (constrained JSON only)
If you want production safety, don’t let the agent freestyle.
`json
{
"route": "fix_setup" | "activate" | "expand" | "retain" | "recover" | "handoff",
"cadence": 0 | 1 | 3 | 5,
"channel": "email" | "push" | "sms" | "in_app" | "wait",
"message_variant_id": "string",
"focus": "one_thing",
"reason": "one sentence"
}
`
Two rules:
1) Short menus only. The agent chooses from allowed values.
2) Always return `reason`. If you can’t explain it, you can’t scale it.
Connected Content example
Fetch a user state blob from your API and normalize it into context_signals.
`liquid
{% connected_content https://api.yourcompany.com/user_state?external_id={{${user_id}}}
:method get
:save user_state
:cache_max_age 900
:retry
%}
{
"attributes": [
{
"external_id": "{{${user_id}}}",
"context_signals": {
"fetch_ok": {% if user_state != nil %}true{% else %}false{% endif %},
"source": "connected_content:user_state",
"as_of": "{{ 'now' | date: '%Y-%m-%dT%H:%M:%SZ' }}",
"lifecycle_stage": "{{ user_state.lifecycle_stage | default: 'unknown' }}",
"quiet_hours": {{ user_state.quiet_hours | default: false }},
"freq_cap_ok": {{ user_state.freq_cap_ok | default: true }},
"friction": {
"verification_pending": {{ user_state.verification_pending | default: false }},
"payment_error": {{ user_state.payment_error | default: false }},
"missing_setup_step": "{{ user_state.missing_setup_step | default: '' }}"
}
}
}
]
}
`
The Canvas build (visual routing)
`text
[Entry Event]
|
v
[User Update: fetch + write context_signals]
|
v
[Agent Step: write agent_decision]
|
+--> (route = fix_setup) --> [Fix Setup Path]
|
+--> (route = activate) --> [Activation Path]
|
+--> (route = retain) --> [Retention Path]
|
+--> (route = handoff) --> [Internal Escalation + Wait]
`
Mapping (what the agent controls)
route→ branching and path selection (stops one-size-fits-all lifecycle)cadence→ delays and message count (controls fatigue and contact pressure)channel→ channel steps (meets the moment, not just the copy)message_variant_id→ block/variant selection (personalization without risky freeform)handoff→ internal event + pause (prevents brand damage)
7 implementable use cases (with control knobs)
1) The cadence governor (highest leverage)
Controls: channel, cadence
- If quiet hours or fatigue risk →
channel=wait - If attention is high →
cadence=1 - If low intent / high risk →
cadence=0(pause)
Implementation tip: store a simple pressure score in signals so this isn’t vibes.
2) Friction-first routing (fix the blocker)
Controls: route=fix_setup, focus, message_variant_id
- Agent picks the single missing step
- Canvas delivers one fix message
- Re-check signals 30–60 minutes later
3) Next-best-action single-threading
Controls: focus, message_variant_id
- Agent chooses exactly one CTA from an action library
- Hold the CTA across channels until completion or expiry
4) Proof-aware asks (celebrate vs ask)
Controls: route, message_variant_id
- If a success moment happened → “celebrate” variant
- If there’s friction/support risk → “stabilize” variant
5) Availability-aware variants (Catalogs + mapping)
Controls: message_variant_id
- Agent chooses a pre-approved block based on eligibility/availability
- You avoid invented promises
6) Repeat-user intelligence
Controls: route, cadence
- First-timers: higher guidance
- Repeat operators: fewer touches, higher signal-to-noise
7) Risk-aware escalation
Controls: route=handoff, channel=wait
- Trigger an internal event/task
- Pause outbound messaging until a human resolves the risk
Minimum viable build (what to do next week)
Day 1: define context_signals + allowed routes
Day 2: Connected Content fetch in a User Update step (write snapshot)
Day 3: agent evaluates → writes agent_decision
Day 4: Canvas branches on agent_decision.route
Day 5: logging + review dashboard + shadow mode holdout
What to measure (so you’re not guessing)
- Route distribution (% routed to each path)
- “Wait” rate (if it’s 0%, your agent isn’t allowed to be cautious)
- Message volume per user (contact pressure)
- Outcome by route (activation / conversion / retention)
- Complaint/unsub deltas
If you do only one thing
Start with the cadence governor or friction-first routing.
They’re boring. They’re defensible. And they pay for themselves fast.
That’s the BRCG way: systems that ship, not decks that impress.
