Understanding AI Agent Referral Programs
AI agent referral programs are structured incentive systems that leverage existing users to drive adoption of AI-powered agents — whether they're chatbots, autonomous assistants, or API-based reasoning tools. Unlike traditional software referral programs, these systems often involve the agent itself acting as the referral engine: detecting usage patterns, identifying ideal moments to suggest sharing, and even generating personalized referral links complete with context-rich previews for potential new users.
At their core, these programs treat word-of-mouth growth as an engineering problem rather than a purely marketing one. When an AI agent delivers real value — solving a complex coding problem, negotiating a better price, or automating a tedious workflow — that moment of delight becomes fertile ground for a referral invitation. The agent can intelligently time the ask, tailor the message to the recipient's context, and track the entire viral loop from impression to conversion.
Key Components of an AI Agent Referral System
A production-ready referral system for AI agents typically involves these interconnected pieces:
- Referral Link Generation — Creating unique, trackable URLs tied to specific users and, optionally, specific agent interactions
- Attribution Tracking — Recording referral source, medium, and campaign data across sessions and devices
- Reward Logic — Defining and fulfilling incentives for both referrer and referee, often in the form of credits, API tokens, or premium features
- Fraud Prevention — Detecting self-referral abuse, bot-driven signups, and other gaming behaviors
- Agent Integration Layer — The mechanism by which the agent decides when and how to surface the referral invitation
Why Referral Programs Matter for AI Agents
AI agents face a unique adoption challenge. They're often invisible in operation — running in background processes, API calls, or chat windows — and their value can be difficult to articulate in traditional ad copy. A static landing page can't demonstrate the fluid intelligence of a well-tuned agent the way a live interaction can. This is where referrals become transformative.
When an existing user shares an agent with a colleague, they rarely just send a link. They share context: "This agent helped me refactor my entire codebase in an afternoon" or "It negotiated my SaaS contracts and saved me 40%." That narrative framing, paired with the recipient's existing trust in the sender, dramatically increases conversion rates compared to cold acquisition channels.
Furthermore, AI agent usage tends to be sticky once integrated into a workflow. A developer who starts using a coding agent for daily tasks is likely to remain an active user for months or years. Referral-acquired users often come pre-educated by their inviter, reducing onboarding friction and support costs. The lifetime value math strongly favors referral growth over paid acquisition for most agent products.
Building the Referral Infrastructure
Let's walk through a practical implementation. We'll build a referral system for an AI coding agent, covering both the backend tracking layer and the agent-side integration that decides when to surface referral prompts.
Database Schema for Referral Tracking
Start with a robust schema that captures the full referral lifecycle. Here's a PostgreSQL migration that establishes the core tables:
-- Migration: create_referral_tables.sql
CREATE TABLE referral_codes (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
code VARCHAR(16) UNIQUE NOT NULL,
is_active BOOLEAN DEFAULT true,
max_uses INTEGER DEFAULT 0, -- 0 means unlimited
current_uses INTEGER DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT now(),
expires_at TIMESTAMPTZ, -- NULL means never expires
metadata JSONB DEFAULT '{}'
);
CREATE TABLE referral_links (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
referrer_user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
referral_code_id UUID NOT NULL REFERENCES referral_codes(id),
share_token VARCHAR(64) UNIQUE NOT NULL,
channel VARCHAR(32) DEFAULT 'direct', -- direct, email, slack, twitter
campaign_id VARCHAR(64), -- for grouping referral sources
context_data JSONB DEFAULT '{}', -- stores agent interaction context
created_at TIMESTAMPTZ DEFAULT now(),
total_clicks INTEGER DEFAULT 0,
total_conversions INTEGER DEFAULT 0
);
CREATE TABLE referral_events (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
referral_link_id UUID REFERENCES referral_links(id),
event_type VARCHAR(32) NOT NULL, -- click, signup, activation, reward
ip_address INET,
user_agent TEXT,
referee_user_id UUID REFERENCES users(id),
reward_amount DECIMAL(10,2),
reward_currency VARCHAR(8) DEFAULT 'credits',
created_at TIMESTAMPTZ DEFAULT now()
);
CREATE INDEX idx_referral_events_link ON referral_events(referral_link_id);
CREATE INDEX idx_referral_events_referee ON referral_events(referee_user_id);
CREATE INDEX idx_referral_codes_user ON referral_codes(user_id);
This schema separates concerns: referral codes are owned by users and can be reused across multiple links. Each referral link captures the specific sharing context — what channel it was shared through, what agent interaction prompted it — while the events table records the full funnel of clicks, signups, and reward distributions.
Generating Referral Links with Contextual Payloads
When the AI agent decides to surface a referral prompt, it should generate a link that carries rich context. Here's a Python function that creates a referral link with embedded interaction data:
import secrets
import hashlib
from datetime import datetime, timedelta
from typing import Optional, Dict, Any
import json
def generate_referral_link(
referrer_user_id: str,
channel: str = "agent_prompt",
context_data: Optional[Dict[str, Any]] = None,
expires_in_days: int = 30
) -> dict:
"""
Creates a referral link with embedded context about the agent interaction
that triggered the referral prompt. Returns the link object for database
insertion and a shareable URL.
"""
# Generate a cryptographically random share token
share_token = secrets.token_urlsafe(48)
# Hash it for database storage (prevents plaintext token exposure)
token_hash = hashlib.sha256(share_token.encode()).hexdigest()
# Build context payload if provided
if context_data is None:
context_data = {}
# Standardize the context structure
context_payload = {
"agent_id": context_data.get("agent_id", "default"),
"interaction_type": context_data.get("interaction_type", "general"),
"summary": context_data.get("summary", ""),
"result_preview": context_data.get("result_preview", ""),
"prompt_reason": context_data.get("prompt_reason", "value_demonstrated"),
"timestamp_iso": datetime.utcnow().isoformat(),
"thread_id": context_data.get("thread_id"),
"model_version": context_data.get("model_version", "unknown")
}
expires_at = datetime.utcnow() + timedelta(days=expires_in_days)
referral_link_record = {
"referrer_user_id": referrer_user_id,
"share_token_hash": token_hash,
"share_token_plain": share_token, # Return to caller for URL construction
"channel": channel,
"context_data": context_payload,
"created_at": datetime.utcnow(),
"expires_at": expires_at,
"total_clicks": 0,
"total_conversions": 0
}
# Construct the full referral URL
base_url = "https://agent.example.com/refer"
referral_url = f"{base_url}/{share_token}"
return {
"record": referral_link_record,
"url": referral_url,
"token": share_token
}
Attribution Middleware for Web Applications
When a referred visitor arrives, you need to persist attribution data across their session — even if they browse around before signing up. Here's a FastAPI middleware that handles referral tracking on the server side:
from fastapi import FastAPI, Request, Response, HTTPException
from fastapi.responses import JSONResponse
from starlette.middleware.base import BaseHTTPMiddleware
import hashlib
import json
from datetime import datetime
class ReferralAttributionMiddleware(BaseHTTPMiddleware):
"""
Middleware that detects referral tokens in incoming requests,
validates them against the database, and persists attribution
data in a signed cookie for later conversion tracking.
"""
def __init__(self, app, db_session_factory, cookie_name="ref_attr"):
super().__init__(app)
self.db_session_factory = db_session_factory
self.cookie_name = cookie_name
async def dispatch(self, request: Request, call_next):
# Extract referral token from URL path or query params
token = None
path_parts = request.url.path.strip("/").split("/")
if len(path_parts) >= 2 and path_parts[0] == "refer":
token = path_parts[1]
if token:
token_hash = hashlib.sha256(token.encode()).hexdigest()
async with self.db_session_factory() as db:
result = await db.execute(
"""
SELECT rl.id, rl.referrer_user_id, rl.context_data, rl.channel
FROM referral_links rl
WHERE rl.share_token_hash = :hash
AND rl.expires_at > :now
""",
{"hash": token_hash, "now": datetime.utcnow()}
)
link = result.fetchone()
if link:
# Record the click event
await db.execute(
"""
INSERT INTO referral_events
(referral_link_id, event_type, ip_address, user_agent)
VALUES (:link_id, 'click', :ip, :ua)
""",
{
"link_id": link.id,
"ip": request.client.host if request.client else None,
"ua": request.headers.get("user-agent", "")
}
)
# Update click counter
await db.execute(
"UPDATE referral_links SET total_clicks = total_clicks + 1 WHERE id = :id",
{"id": link.id}
)
await db.commit()
# Build attribution payload for the cookie
attribution_data = {
"referral_link_id": str(link.id),
"referrer_user_id": str(link.referrer_user_id),
"channel": link.channel,
"landed_at": datetime.utcnow().isoformat(),
"token_hash": token_hash
}
# Create a response and set the attribution cookie
response = await call_next(request)
response.set_cookie(
self.cookie_name,
json.dumps(attribution_data),
max_age=86400 * 30, # 30 days
httponly=True,
secure=True,
samesite="lax"
)
return response
# No referral token — check if attribution cookie already exists
existing_attr = request.cookies.get(self.cookie_name)
response = await call_next(request)
if existing_attr:
# Refresh the cookie's max_age to extend attribution window
response.set_cookie(
self.cookie_name,
existing_attr,
max_age=86400 * 30,
httponly=True,
secure=True,
samesite="lax"
)
return response
Conversion Tracking on Signup
The final piece of the attribution puzzle fires when a referred visitor converts. This function reads the attribution cookie, validates it hasn't been tampered with, and records the conversion event along with reward distribution:
async def process_referral_conversion(
db_session,
referee_user_id: str,
attribution_cookie: str,
reward_amount: float = 10.0,
reward_currency: str = "credits"
) -> dict:
"""
Called upon successful user signup. Reads the attribution cookie,
validates the referral link, marks the conversion, and distributes
rewards to both referrer and referee.
Returns a summary of actions taken.
"""
try:
attribution_data = json.loads(attribution_cookie)
except (json.JSONDecodeError, TypeError):
return {"status": "no_attribution", "message": "Invalid attribution cookie"}
required_fields = ["referral_link_id", "referrer_user_id", "token_hash"]
if not all(field in attribution_data for field in required_fields):
return {"status": "no_attribution", "message": "Missing attribution fields"}
referral_link_id = attribution_data["referral_link_id"]
referrer_user_id = attribution_data["referrer_user_id"]
async with db_session() as db:
# Verify the referral link is still valid
result = await db.execute(
"""
SELECT id, referrer_user_id, referral_code_id, total_conversions
FROM referral_links
WHERE id = :link_id
AND referrer_user_id = :referrer_id
AND expires_at > :now
""",
{
"link_id": referral_link_id,
"referrer_id": referrer_user_id,
"now": datetime.utcnow()
}
)
link = result.fetchone()
if not link:
return {"status": "expired_or_invalid", "message": "Referral link is no longer valid"}
# Check for duplicate conversion (same referee can't convert twice on same link)
duplicate_check = await db.execute(
"""
SELECT id FROM referral_events
WHERE referral_link_id = :link_id
AND referee_user_id = :referee_id
AND event_type = 'signup'
""",
{"link_id": referral_link_id, "referee_id": referee_user_id}
)
if duplicate_check.fetchone():
return {"status": "duplicate", "message": "Conversion already recorded for this user"}
# Record the conversion event
await db.execute(
"""
INSERT INTO referral_events
(referral_link_id, event_type, referee_user_id, reward_amount, reward_currency)
VALUES (:link_id, 'signup', :referee_id, :amount, :currency)
""",
{
"link_id": referral_link_id,
"referee_id": referee_user_id,
"amount": reward_amount,
"currency": reward_currency
}
)
# Update conversion counter
await db.execute(
"UPDATE referral_links SET total_conversions = total_conversions + 1 WHERE id = :id",
{"id": referral_link_id}
)
# Distribute rewards (idempotent operation — use a transaction)
# Reward the referrer
await db.execute(
"""
UPDATE users SET credit_balance = credit_balance + :amount
WHERE id = :user_id
""",
{"amount": reward_amount, "user_id": referrer_user_id}
)
# Reward the referee (new user)
await db.execute(
"""
UPDATE users SET credit_balance = credit_balance + :amount
WHERE id = :user_id
""",
{"amount": reward_amount * 0.5, "user_id": referee_user_id}
)
# Record reward events
await db.execute(
"""
INSERT INTO referral_events
(referral_link_id, event_type, referee_user_id, reward_amount, reward_currency)
VALUES
(:link_id, 'reward_referrer', :referee_id, :ref_amount, :currency)
""",
{
"link_id": referral_link_id,
"referee_id": referee_user_id,
"ref_amount": reward_amount,
"currency": reward_currency
}
)
await db.commit()
return {
"status": "converted",
"referral_link_id": str(referral_link_id),
"referrer_rewarded": reward_amount,
"referee_rewarded": reward_amount * 0.5,
"currency": reward_currency
}
Integrating the Referral Prompt into the AI Agent
The most distinctive aspect of an AI agent referral program is how the agent itself decides to surface the referral invitation. This requires careful prompt engineering and context evaluation to avoid annoying users with poorly timed requests.
Agent-Side Referral Decision Logic
Here's a Python implementation that evaluates conversation context and determines whether to inject a referral suggestion into the agent's response stream:
from dataclasses import dataclass, field
from enum import Enum
from typing import List, Optional
import time
class ReferralSentiment(Enum):
POSITIVE = "positive"
NEUTRAL = "neutral"
NEGATIVE = "negative"
@dataclass
class InteractionContext:
user_id: str
thread_id: str
message_count: int
user_satisfaction_signals: List[str] = field(default_factory=list)
explicit_thanks: bool = False
problem_resolved: bool = False
time_spent_minutes: float = 0.0
agent_performance_score: float = 0.0 # 0-1 scale from internal evaluation
class ReferralPromptDecider:
"""
Decides whether an AI agent should surface a referral prompt
based on conversation quality, user sentiment, and timing constraints.
"""
def __init__(
self,
min_messages_before_prompt: int = 6,
min_satisfaction_score: float = 0.75,
cooldown_minutes: int = 120,
max_prompts_per_user_per_day: int = 2,
exclude_negative_sentiment: bool = True
):
self.min_messages = min_messages_before_prompt
self.min_satisfaction = min_satisfaction_score
self.cooldown_minutes = cooldown_minutes
self.max_prompts_per_day = max_prompts_per_user_per_day
self.exclude_negative = exclude_negative_sentiment
self._prompt_history = {} # user_id -> list of timestamps
def should_prompt(self, context: InteractionContext) -> tuple[bool, Optional[str]]:
"""
Evaluates whether to show a referral prompt and returns
a decision along with an optional reason string.
"""
user_id = context.user_id
# Check message volume — need meaningful interaction
if context.message_count < self.min_messages:
return False, "insufficient_interaction_depth"
# Check satisfaction threshold
if context.agent_performance_score < self.min_satisfaction:
return False, "below_satisfaction_threshold"
# Don't prompt during negative experiences
if self.exclude_negative:
negative_signals = ["frustrated", "angry", "confused", "error", "bug"]
for signal in context.user_satisfaction_signals:
if signal.lower() in negative_signals:
return False, "negative_sentiment_detected"
# Enforce cooldown between prompts
now = time.time()
user_prompts = self._prompt_history.get(user_id, [])
# Clean old entries outside cooldown window
user_prompts = [
ts for ts in user_prompts
if (now - ts) < (self.cooldown_minutes * 60)
]
if user_prompts:
last_prompt_ago = now - user_prompts[-1]
if last_prompt_ago < (self.cooldown_minutes * 60):
return False, f"cooldown_active_{int(last_prompt_ago)}s_ago"
# Check daily cap
daily_prompts = [
ts for ts in self._prompt_history.get(user_id, [])
if (now - ts) < 86400 # 24 hours
]
if len(daily_prompts) >= self.max_prompts_per_day:
return False, "daily_prompt_cap_reached"
# All checks passed — recommend prompting
return True, "conditions_met"
def record_prompt_shown(self, user_id: str):
"""Record that a prompt was displayed to enforce cooldown and caps."""
now = time.time()
if user_id not in self._prompt_history:
self._prompt_history[user_id] = []
self._prompt_history[user_id].append(now)
# Prune entries older than 24 hours to prevent memory bloat
self._prompt_history[user_id] = [
ts for ts in self._prompt_history[user_id]
if (now - ts) < 86400
]
Prompt Template Engineering for the Referral Message
The referral message itself should feel native to the agent's voice and contextually relevant. Here's how to construct a dynamic prompt template that the agent can inject into its response:
def build_referral_prompt_template(
context: InteractionContext,
referral_url: str,
agent_name: str = "CodeCompanion"
) -> str:
"""
Constructs a context-aware referral prompt that feels natural
within the agent's conversational flow. Returns a string that
can be appended to the agent's response.
"""
# Base templates for different interaction types
templates = {
"problem_solved": (
f"\n\nI'm glad I could help with this! 🎉\n"
f"If you found this useful, you can share {agent_name} with a teammate "
f"using your referral link. They'll get extra credits to start, and "
f"so will you: {referral_url}\n"
f"(I only mention this when I'm confident I've actually been helpful — "
f"feel free to ignore if now's not the right time!)"
),
"workflow_automation": (
f"\n\nBy the way — since this automation saved you some serious time, "
f"you might know someone else on your team who'd benefit from {agent_name}.\n"
f"Here's your referral link if you'd like to share: {referral_url}\n"
f"Both you and they will receive bonus credits when they sign up."
),
"learning_assistance": (
f"\n\nI hope this explanation was clear! If you have colleagues learning "
f"similar concepts, {agent_name} can help them too.\n"
f"Your referral link (with bonus credits for both of you): {referral_url}"
),
"general_value": (
f"\n\nThanks for the great conversation! If you know someone who could "
f"benefit from {agent_name}, here's your referral link: {referral_url}\n"
f"You'll both receive credits when they join."
)
}
# Select template based on interaction type
interaction_type = context.agent_performance_score > 0.9 and "problem_solved" \
or "general_value"
if context.explicit_thanks:
interaction_type = "problem_solved"
template = templates.get(interaction_type, templates["general_value"])
return template
Full Agent Response Pipeline with Referral Injection
Here's the complete integration that ties together the decision logic, prompt construction, and link generation into a single agent response pipeline:
class AgentReferralPipeline:
"""
Orchestrates the full referral flow: evaluates context, generates
referral links when appropriate, and injects prompts into agent responses.
"""
def __init__(self, db_session_factory, agent_name="CodeCompanion"):
self.db_factory = db_session_factory
self.decider = ReferralPromptDecider()
self.agent_name = agent_name
async def process_agent_response(
self,
user_id: str,
thread_id: str,
agent_response_text: str,
interaction_context: InteractionContext
) -> str:
"""
Takes the agent's intended response and optionally appends
a referral prompt if conditions are met. Returns the final
response text to be shown to the user.
"""
# Evaluate whether to prompt
should_prompt, reason = self.decider.should_prompt(interaction_context)
if not should_prompt:
# Log the decision for analytics (omitted for brevity)
return agent_response_text
# Generate a referral link with context
link_data = generate_referral_link(
referrer_user_id=user_id,
channel="agent_prompt",
context_data={
"agent_id": self.agent_name,
"interaction_type": "problem_solved" if interaction_context.explicit_thanks else "general",
"summary": agent_response_text[:200], # Truncated for payload size
"prompt_reason": reason,
"thread_id": thread_id,
"model_version": "v2.4.1"
}
)
# Store the referral link in the database
async with self.db_factory() as db:
await db.execute(
"""
INSERT INTO referral_links
(referrer_user_id, referral_code_id, share_token, channel, context_data)
VALUES (:user_id,
(SELECT id FROM referral_codes WHERE user_id = :user_id AND is_active = true LIMIT 1),
:token_hash, :channel, :context)
""",
{
"user_id": user_id,
"token_hash": link_data["record"]["share_token_hash"],
"channel": "agent_prompt",
"context": json.dumps(link_data["record"]["context_data"])
}
)
await db.commit()
# Build the referral prompt
prompt_text = build_referral_prompt_template(
interaction_context,
link_data["url"],
self.agent_name
)
# Record that we showed a prompt
self.decider.record_prompt_shown(user_id)
# Return the agent response with the prompt appended
final_response = agent_response_text + "\n" + prompt_text
return final_response
Fraud Prevention and Program Integrity
Referral programs attract abuse. Self-referrals (creating fake accounts to claim rewards), bot-driven signups, and reward farming rings can drain your budget and pollute your user base. Building defenses into the system from day one is essential.
Multi-Layer Fraud Detection
Implement a fraud scoring system that evaluates each conversion attempt across multiple signals:
from dataclasses import dataclass
from typing import List, Optional
import ipaddress
import re
@dataclass
class FraudAssessment:
score: float # 0.0 (clean) to 1.0 (certain fraud)
flags: List[str]
recommended_action: str # "allow", "review", "block"
class ReferralFraudDetector:
"""
Multi-signal fraud detection for referral conversions.
Combines IP analysis, velocity checks, device fingerprinting,
and behavioral patterns to assign fraud scores.
"""
def __init__(
self,
high_risk_ip_ranges: List[str] = None,
block_threshold: float = 0.8,
review_threshold: float = 0.4
):
self.high_risk_ranges = high_risk_ip_ranges or [
"127.0.0.0/8", # localhost abuse
]
self.block_threshold = block_threshold
self.review_threshold = review_threshold
# Convert CIDR ranges to ipaddress objects
self._parsed_ranges = [
ipaddress.ip_network(r) for r in self.high_risk_ranges
]
async def assess_conversion(
self,
db_session,
referrer_user_id: str,
referee_ip: str,
referee_user_agent: str,
referee_email: str,
referral_link_id: str
) -> FraudAssessment:
"""
Evaluates a conversion attempt and returns a fraud assessment.
"""
flags = []
score = 0.0
# Check 1: IP address analysis
try:
ip_addr = ipaddress.ip_address(referee_ip)
for network in self._parsed_ranges:
if ip_addr in network:
flags.append("high_risk_ip_range")
score += 0.3
except ValueError:
flags.append("invalid_ip")
score += 0.2
# Check 2: Check if referrer and referee share IP address
async with db_session() as db:
result = await db.execute(
"""
SELECT ip_address FROM referral_events
WHERE referral_link_id = :link_id AND event_type = 'click'
ORDER BY created_at DESC LIMIT 1
""",
{"link_id": referral_link_id}
)
referrer_click = result.fetchone()
if referrer_click and referrer_click.ip_address:
if str(ip_addr) == str(referrer_click.ip_address):
flags.append("same_ip_as_referrer")
score += 0.4 # Strong signal of self-referral
# Check 3: Velocity — how many conversions from this referrer recently?
result = await db.execute(
"""
SELECT COUNT(*) as conv_count
FROM referral_events re
JOIN referral_links rl ON re.referral_link_id = rl.id
WHERE rl.referrer_user_id = :referrer_id
AND re.event_type = 'signup'
AND re.created_at > NOW() - INTERVAL '24 hours'
""",
{"referrer_id": referrer_user_id}
)
recent_conversions = result.fetchone().conv_count
if recent_conversions > 5:
flags.append(f"high_velocity_{recent_conversions}_24h")
score += min(0.5, recent_conversions * 0.1)
# Check 4: Email domain analysis
email_domain = referee_email.split("@")[-1].lower()
disposable_domains = {
"mailinator.com", "guerrillamail.com", "tempmail.com",
"10minutemail.com", "yopmail.com", "sharklasers.com",
"spamgourmet.com", "trashmail.com"
}
if email_domain in disposable_domains:
flags.append("disposable_email")
score += 0.3
# Check 5: User agent consistency
known_bot_agents = ["headless", "selenium", "puppeteer", "playwright"]
ua_lower = referee_user_agent.lower()
for bot_signal in known_bot_agents:
if bot_signal in ua_lower:
flags.append("bot_user_agent")
score += 0.3
break
# Determine action based on score
if score >= self.block_threshold:
action = "block"
elif score >= self.review_threshold:
action = "review"
else:
action = "allow"
return FraudAssessment(
score=score,
flags=flags,
recommended_action=action
)
Best Practices for AI Agent Referral Programs
Building the technical infrastructure is only half the equation. These practices separate successful referral programs from those that annoy users or collapse under abuse.
Timing Is Everything
The most common failure mode is prompting for referrals at the wrong moment. An agent that asks for a referral after failing to solve a problem destroys trust. Conversely, an agent that never asks leaves growth on the table. Implement explicit satisfaction signals — the user saying "thanks, that was perfect" or the agent successfully completing a multi-step task — as gating conditions. The ReferralPromptDecider class above enforces this, but you should continuously tune thresholds based on actual conversion data.
Respect the User's Attention
Cooldowns and daily caps aren't just technical safeguards — they're UX requirements. A user who sees three referral prompts in a single session will mentally mute the agent entirely. The cooldown mechanism should be conservative: 2-4 hours between prompts is a reasonable starting point. Additionally, provide a persistent, low-friction way for users to access their referral link on demand (a dashboard widget, a slash command like /referral) so they aren't dependent on the agent's timing.
Make Rewards Meaningful and Immediate
Credit-based rewards work well for developer-focused agents. The reward should be substantial enough to motivate sharing — 10-20% of a typical monthly usage allowance is a good benchmark. Crucially, rewards must be granted instantly upon conversion, not batched weekly. The psychological reinforcement of seeing credits appear immediately after a colleague signs up creates a positive feedback loop that encourages further sharing.
Transparency Builds Trust
Never hide the fact that a message is a referral prompt. The templates in the build_referral_prompt_template function explicitly acknowledge the promotional nature: "I only mention this when I'm confident I've actually been helpful." This honesty respects the user's intelligence and actually increases conversion rates compared to manipulative or sneaky approaches. Provide a clear opt-out mechanism — a simple "Don't ask me about referrals" preference that the agent honors globally.
Monitor and Iterate on Metrics
Track the full funnel: impressions → clicks → signups → activations → referrer retention. A referral program that drives signups but those users never activate is worse than no program at all — it pollutes your user base. Build dashboards that surface the conversion rate at each stage, broken down by agent interaction type, channel, and user segment. Use this data to refine both the prompt timing logic and the reward structure.
Implement Graduated Reward Tiers
Simple flat rewards work initially, but sophisticated programs introduce tiers that reward power users who bring in high-quality referrals. Consider implementing a system where the reward increases when the referred user reaches certain milestones — for example, completing 10 agent interactions, or remaining active for 30 days. This aligns incentives: referrers are motivated to bring in users who will actually use the product, not just sign up and abandon it.
# Example graduated reward configuration
GRADUATED_REWARDS = {
"signup": 10.0, # Immediate reward on signup
"first_interaction": 5.0, # Bonus when referee completes first agent task
"power_user": 25.0, # Bonus when referee reaches 50 interactions
"monthly_active": 15.0, # Bonus when referee is active for 30+ days
}
async def evaluate_graduated_reward(
db_session,
referee_user_id: str,
milestone: str,
referral_link_id: str
) -> Optional[float]:
"""
Checks if a graduated reward milestone has been reached
and hasn't already been awarded for this referral link.
"""
reward_amount = GRADUATED_REWARDS.get(milestone)
if not reward_amount:
return None
async with db_session() as db:
# Check for duplicate reward
result = await db.execute(
"""
SELECT id FROM referral_events
WHERE referral_link_id = :link_id
AND referee_user_id = :referee_id
AND event_type = :milestone
""",
{"link_id": referral_link_id, "referee_id": referee_user_id, "milestone": milestone}
)
if result.fetch