Archety Amplitude Tracking Sheet¶
Table of Contents¶
Tracking Strategy & Principles¶
Core Principles¶
- No PII / Content: Never send raw message text, email subjects, or personal content. Only counts, types, lengths, and high-level labels.
- Flat, Boring Names: Use consistent snake_case like
user_signed_up,message_sent,superpower_triggered. - One Event Per Action: Avoid mega-events. Keep LLM usage separate from messaging.
- Consistent Properties: Always include
persona_id,channel,is_group_threadwhere applicable.
Data Privacy¶
- Message content: ❌ Never tracked
- Message length: ✅ Tracked as integer
- Calendar event titles: ❌ Never tracked
- Calendar event count: ✅ Tracked
- Email subjects: ❌ Never tracked
- Email sender domains: ✅ Tracked (anonymized)
Identity Model¶
User Identification¶
Properties:
- user_id: Internal UUID (primary identifier)
- phone: Hashed phone number (for correlation only, not PII)
- device_id: Automatically handled by Amplitude SDK
Channel Identity¶
Track as event properties, not separate user IDs:
- primary_channel: imessage | telegram | web | ios_app | android_app
- channel_source: imessage | telegram_webhook | edge_agent | backend
Groups (Future)¶
group_type=thread,group_id=chat_guidfor thread-level analyticsgroup_type=org,group_id=org_123for B2B (when launched)
User Properties¶
Track via amplitude.identify() calls - updated on changes, not every event.
| Property | Type | Description | Update Trigger |
|---|---|---|---|
signup_date |
ISO date | First message timestamp | On user creation |
primary_persona |
string | sage | vex | echo |
On first message |
primary_channel |
string | imessage | telegram |
On first message |
relationship_stage |
string | stranger | acquaintance | friend | best_friend |
On relationship update |
trust_score |
int (0-100) | Relationship trust level | On relationship update |
rapport_score |
int (0-100) | Relationship rapport level | On relationship update |
has_group_threads |
boolean | Sage in ≥1 group chat | On first group message |
superpowers_enabled_count |
int | # of enabled superpowers | On enable/disable |
calendar_connected |
boolean | Google Calendar OAuth | On OAuth grant/revoke |
gmail_connected |
boolean | Gmail OAuth | On OAuth grant/revoke |
total_messages_sent |
int | Lifetime user→Sage | Daily batch update |
total_messages_received |
int | Lifetime Sage→user | Daily batch update |
total_llm_tokens_used |
int | Lifetime token count | Daily batch update |
total_memories |
int | Total memories in mem0 | Daily batch update |
last_active_date |
ISO date | Most recent message | On each message |
Future (when billing launches):
- plan_tier: free | friend | best_friend | enterprise
- billing_status: active | trialing | canceled | past_due
- lifetime_revenue_usd: Sum of invoices
Event Taxonomy¶
1. Acquisition & Onboarding¶
1.1 user_discovered¶
When: First message from unknown phone number
Implementation: app/main.py::handle_orchestrator_message() - on User creation
{
"event": "user_discovered",
"properties": {
"channel": "imessage" | "telegram",
"persona_id": "sage" | "echo", # Inferred from context
"mode": "direct" | "group",
"has_participants": true # If group
}
}
1.2 phone_verification_completed¶
When: User completes first bidirectional conversation
Implementation: app/orchestrator/relationship_service.py - on stage transition from null→stranger
{
"event": "phone_verification_completed",
"properties": {
"channel": "imessage" | "telegram",
"persona_id": "sage" | "echo"
}
}
1.3 persona_selected¶
When: First interaction with a specific persona
Implementation: app/main.py::handle_orchestrator_message() - on first message per persona
{
"event": "persona_selected",
"properties": {
"persona_id": "sage" | "vex" | "echo",
"channel": "imessage" | "telegram",
"is_primary": true # First persona for this user
}
}
2. Messaging & Engagement¶
2.1 message_sent_user¶
When: Every inbound message from user
Implementation: app/main.py::handle_orchestrator_message() - after dedup check
{
"event": "message_sent_user",
"properties": {
"channel": "imessage" | "telegram",
"persona_id": "sage" | "echo",
"thread_id": "hashed_chat_guid", # SHA-256 hash for privacy
"is_group_thread": true | false,
"message_length_chars": 142,
"message_type": "text" | "photo" | "voice",
"group_size": 5, # Only if is_group_thread=true
"contains_mention": true, # @sage mentioned
"detected_intent": "casual_chat" | "calendar_query" | "email_check" | "task_request",
"workflow_triggered": "calendar_stress" | null
}
}
2.2 message_sent_companion¶
When: Every outbound Sage/Echo message
Implementation:
- app/orchestrator/two_stage_handler.py::handle() - after response generation
- app/messaging/proactive_sender.py::send_to_user() - for proactive messages
{
"event": "message_sent_companion",
"properties": {
"channel": "imessage" | "telegram",
"persona_id": "sage" | "echo",
"thread_id": "hashed_chat_guid",
"is_group_thread": true | false,
"message_length_chars": 87,
"message_category": "reply" | "reflex" | "burst" | "proactive_nudge" | "workflow_result",
"is_reflex": true, # From reflex library
"is_burst": true, # Follow-up bubble
"burst_position": 2, # Position in burst (1, 2, 3...)
"response_latency_ms": 1847, # Time from user message to this response
"used_memories": true, # Referenced mem0 memories
"memory_count": 3, # # of memories used
"memory_types": ["emotional", "factual"], # Memory categories used
"relationship_stage": "friend"
}
}
2.3 conversation_started¶
When: First message after ≥30min inactivity
Implementation: app/orchestrator/conversation_history_service.py - check last message timestamp
{
"event": "conversation_started",
"properties": {
"channel": "imessage" | "telegram",
"persona_id": "sage" | "echo",
"thread_id": "hashed_chat_guid",
"is_group_thread": true | false,
"hours_since_last": 12.5,
"time_of_day": "morning" | "afternoon" | "evening" | "night", # Bucketed
"day_of_week": "monday",
"initiated_by": "user" | "companion" # Who sent first message
}
}
2.4 conversation_ended¶
When: No activity for 30min (detected on next message or batch job) Implementation: Background job or lazy evaluation on next message
{
"event": "conversation_ended",
"properties": {
"channel": "imessage" | "telegram",
"persona_id": "sage" | "echo",
"thread_id": "hashed_chat_guid",
"is_group_thread": true | false,
"duration_sec": 1247,
"user_messages_count": 8,
"companion_messages_count": 12,
"workflow_executions": 2, # # of workflows during session
"memories_created": 3, # New memories added
"relationship_stage_start": "friend",
"relationship_stage_end": "friend" # Changed if progression occurred
}
}
2.5 group_thread_joined¶
When: Sage first added to group chat
Implementation: app/main.py::handle_orchestrator_message() - on first group message detection
{
"event": "group_thread_joined",
"properties": {
"channel": "imessage" | "telegram",
"thread_id": "hashed_chat_guid",
"group_size": 5, # Estimate from participants array
"persona_id": "echo" # Usually Echo for groups
}
}
2.6 group_plan_summarized¶
When: Echo posts plan summary in group ("Plan is 7pm at Dante...")
Implementation: app/superpowers/catalog/on_demand/group_coordination.py - when plan extracted
{
"event": "group_plan_summarized",
"properties": {
"channel": "imessage" | "telegram",
"thread_id": "hashed_chat_guid",
"persona_id": "echo",
"group_size": 5,
"plan_type": "dinner" | "trip" | "party" | "meeting" | "other", # Inferred
"has_time": true,
"has_location": true,
"participant_count": 5
}
}
2.7 reminder_scheduled¶
When: User or backend schedules future reminder
Implementation: app/models/database.py::ScheduledMessage insert
{
"event": "reminder_scheduled",
"properties": {
"channel": "imessage" | "telegram",
"persona_id": "sage",
"thread_id": "hashed_chat_guid",
"reminder_type": "generic" | "flight_departure" | "call_parents" | "deadline",
"send_at_timestamp": "2025-11-10T15:00:00Z",
"hours_until_send": 24.5,
"created_by": "user_request" | "workflow" # Manual vs automated
}
}
2.8 reminder_sent¶
When: Scheduled reminder delivered
Implementation: Scheduler job that processes ScheduledMessage table
{
"event": "reminder_sent",
"properties": {
"channel": "imessage" | "telegram",
"persona_id": "sage",
"thread_id": "hashed_chat_guid",
"reminder_type": "generic" | "flight_departure",
"scheduled_at": "2025-11-10T15:00:00Z",
"sent_at": "2025-11-10T15:00:23Z",
"delay_ms": 23000, # If delayed
"created_by": "user_request" | "workflow"
}
}
3. Superpowers & Integrations¶
3.1 integration_connected¶
When: User completes OAuth flow
Implementation: app/oauth/routes.py::auth_callback() - after token storage
{
"event": "integration_connected",
"properties": {
"integration_type": "gmail" | "google_calendar",
"persona_id": "sage", # Who they were talking to when auth initiated
"channel": "imessage" | "telegram",
"trigger_workflow": "calendar_stress" # Which workflow prompted OAuth
}
}
3.2 integration_disconnected¶
When: User revokes OAuth or token expired
Implementation: app/oauth/routes.py::revoke_access() or token expiry detection
{
"event": "integration_disconnected",
"properties": {
"integration_type": "gmail" | "google_calendar",
"reason": "user_revoked" | "token_expired" | "permission_denied",
"days_connected": 45 # How long it was active
}
}
3.3 superpower_enabled¶
When: User turns on superpower (currently all enabled by default, but track when feature launches)
Implementation: app/superpowers/manager.py::enable_superpower()
{
"event": "superpower_enabled",
"properties": {
"superpower_id": "travel_brain" | "calendar_stress_shield" | "email_urgency_scan",
"superpower_category": "travel" | "productivity" | "social" | "money",
"requires_oauth": true,
"persona_id": "sage",
"channel": "imessage" | "telegram",
"has_required_oauth": true # Whether user already has needed OAuth
}
}
3.4 superpower_disabled¶
When: User turns off superpower
Implementation: app/superpowers/manager.py::disable_superpower()
{
"event": "superpower_disabled",
"properties": {
"superpower_id": "travel_brain",
"superpower_category": "travel",
"persona_id": "sage",
"channel": "imessage" | "telegram",
"days_enabled": 30, # How long it was on
"usage_count": 12 # How many times it triggered while enabled
}
}
3.5 superpower_triggered¶
When: Backend decides to run superpower for user
Implementation:
- app/scheduler/background_service.py - for proactive workflows
- app/messaging/workflow_detector.py::detect_workflow() - for on-demand
{
"event": "superpower_triggered",
"properties": {
"superpower_id": "calendar_stress_shield",
"superpower_category": "productivity",
"trigger_source": "scheduled" | "user_request" | "event_based",
"trigger_time": "07:30", # For scheduled
"channel": "telegram",
"persona_id": "sage",
"has_required_oauth": true,
"workflow_id": "proactive_morning_calendar"
}
}
3.6 superpower_result_sent¶
When: Sage sends superpower outcome to user
Implementation: app/superpowers/engine.py::execute_workflow() - on completion
{
"event": "superpower_result_sent",
"properties": {
"superpower_id": "calendar_stress_shield",
"superpower_category": "productivity",
"channel": "telegram",
"persona_id": "sage",
"result_type": "suggestion_only" | "reminder_scheduled" | "data_summary" | "no_action_needed",
"execution_duration_ms": 3421,
"data_items_found": 5, # e.g., # of calendar events
"action_suggested": true,
"workflow_id": "proactive_morning_calendar"
}
}
3.7 superpower_accepted¶
When: User agrees to suggested action
Implementation: app/superpowers/engine.py - when user responds positively to paused workflow
{
"event": "superpower_accepted",
"properties": {
"superpower_id": "calendar_stress_shield",
"superpower_category": "productivity",
"action_type": "block_calendar" | "send_draft" | "schedule_reminder" | "other",
"channel": "telegram",
"persona_id": "sage",
"response_time_sec": 120 # How long user took to respond
}
}
3.8 superpower_rejected¶
When: User says no or ignores
Implementation: app/superpowers/engine.py - negative response or timeout
{
"event": "superpower_rejected",
"properties": {
"superpower_id": "calendar_stress_shield",
"superpower_category": "productivity",
"rejection_type": "explicit_no" | "ignored" | "timeout",
"channel": "telegram",
"persona_id": "sage",
"response_time_sec": null # null if ignored
}
}
4. LLM Usage & Costs¶
4.1 llm_request_started¶
When: Right before LLM API call (optional - useful for latency tracking)
Implementation: app/utils/llm_client.py::generate() - start of method
{
"event": "llm_request_started",
"properties": {
"request_id": "uuid-v4",
"persona_id": "sage",
"user_id": "user-uuid", # Set as user property
"superpower_id": "calendar_stress_shield" | null,
"prompt_type": "chat_reply" | "reflex_classification" | "workflow_analysis" | "memory_extraction",
"model": "gpt-4o" | "gpt-4o-mini" | "claude-3-5-sonnet",
"stage": "stage_1" | "stage_2" | "single", # Two-stage handler context
"channel": "imessage" | "telegram"
}
}
4.2 llm_request_completed¶
When: After LLM response received
Implementation: app/utils/llm_client.py::generate() - after response
{
"event": "llm_request_completed",
"properties": {
"request_id": "uuid-v4", # Match to started event
"user_id": "user-uuid",
"persona_id": "sage",
"superpower_id": "calendar_stress_shield" | null,
"prompt_type": "chat_reply",
"model": "gpt-4o",
"stage": "stage_2",
"channel": "telegram",
# Token metrics
"prompt_tokens": 1247,
"completion_tokens": 156,
"total_tokens": 1403,
# Cost estimate (calculated client-side)
"llm_cost_usd": 0.0084, # Based on model pricing
# Performance
"latency_ms": 1847,
"status": "success" | "error",
"error_type": "rate_limit" | "timeout" | "invalid_request" | null,
# Context
"conversation_history_messages": 10,
"memories_included": 3,
"workflow_context_included": true
}
}
4.3 llm_cost_summary (Batch Daily Event)¶
When: Daily aggregation of LLM costs per user Implementation: Background job that queries daily totals
{
"event": "llm_cost_summary",
"properties": {
"date": "2025-11-06",
"total_requests": 42,
"total_tokens": 58420,
"total_cost_usd": 2.47,
"cost_by_model": {
"gpt-4o": 2.13,
"gpt-4o-mini": 0.34
},
"cost_by_category": {
"chat_reply": 1.85,
"workflow": 0.52,
"memory_extraction": 0.10
},
"avg_cost_per_message": 0.059
}
}
5. Memory System¶
5.1 memory_created¶
When: New memory stored in mem0
Implementation: app/memory/mem0_service.py::add_memory() - after successful storage
{
"event": "memory_created",
"properties": {
"persona_id": "sage",
"channel": "imessage",
"memory_category": "emotional" | "factual" | "deadline" | "plan" | "preference",
"namespace_type": "direct" | "group" | "persona", # Which namespace
"is_group_memory": false,
"conversation_context": "casual_chat" | "workflow_result",
"memory_length_chars": 87, # Approximate
"total_memories_for_user": 145 # Running count
}
}
5.2 memory_retrieved¶
When: Memories fetched from mem0 for context
Implementation: app/memory/mem0_service.py::search_memories() - after retrieval
{
"event": "memory_retrieved",
"properties": {
"persona_id": "sage",
"channel": "imessage",
"namespace_type": "direct" | "group",
"memories_count": 3,
"memory_categories": ["emotional", "factual"], # Categories of retrieved memories
"search_query": "recent_conversation" | "relevant_context" | "specific_topic",
"retrieval_latency_ms": 234
}
}
5.3 memory_forgotten¶
When: User explicitly asks to forget something
Implementation: app/memory/boundary_manager.py::handle_forget_command() - on detection
{
"event": "memory_forgotten",
"properties": {
"persona_id": "sage",
"channel": "imessage",
"forget_type": "specific" | "general", # "forget that" vs "forget about my ex"
"namespace_type": "direct" | "group",
"memories_deleted": 2 # # of memories removed
}
}
5.4 boundary_set¶
When: User sets conversation boundary in group
Implementation: app/memory/boundary_manager.py::handle_boundary_command() - on detection
{
"event": "boundary_set",
"properties": {
"persona_id": "echo",
"channel": "imessage",
"thread_id": "hashed_chat_guid",
"boundary_type": "topic_restriction" | "privacy_request",
"boundary_scope": "this_group" # Always group context
}
}
6. Relationship Progression¶
6.1 relationship_stage_changed¶
When: User-persona relationship advances
Implementation: app/orchestrator/relationship_service.py::update_relationship() - on stage change
{
"event": "relationship_stage_changed",
"properties": {
"persona_id": "sage",
"channel": "imessage",
"previous_stage": "stranger",
"new_stage": "acquaintance",
"trust_score": 45,
"rapport_score": 38,
"days_since_first_message": 3,
"total_messages_exchanged": 87,
"inside_jokes_count": 2
}
}
7. Reliability & Infrastructure¶
7.1 message_delivery_failed¶
When: iMessage/Telegram send fails
Implementation: Error handling in app/messaging/telegram.py and relay response
{
"event": "message_delivery_failed",
"properties": {
"channel": "imessage" | "telegram",
"thread_id": "hashed_chat_guid",
"error_code": "network_timeout" | "invalid_recipient" | "rate_limited",
"message_category": "reply" | "proactive_nudge",
"retry_attempt": 1 # If retrying
}
}
7.2 superpower_error¶
When: Workflow execution fails
Implementation: app/superpowers/engine.py::execute_workflow() - error handler
{
"event": "superpower_error",
"properties": {
"superpower_id": "calendar_stress_shield",
"superpower_category": "productivity",
"error_type": "oauth_missing" | "api_error" | "timeout" | "invalid_state",
"integration_type": "gmail" | "google_calendar" | null,
"workflow_id": "proactive_morning_calendar",
"node_id": "fetch_calendar_events" # Which node failed
}
}
7.3 oauth_refresh_failed¶
When: OAuth token refresh fails
Implementation: app/oauth/token_manager.py::refresh_token() - on error
{
"event": "oauth_refresh_failed",
"properties": {
"integration_type": "gmail" | "google_calendar",
"error_code": "invalid_grant" | "network_error",
"days_since_connected": 45,
"needs_reauth": true
}
}
8. Future: Billing & Revenue¶
Note: Not currently implemented in codebase. Wire these when Stripe integration launches.
8.1 subscription_checkout_started¶
8.2 subscription_checkout_completed¶
8.3 subscription_activated¶
8.4 subscription_canceled¶
8.5 invoice_paid¶
8.6 invoice_payment_failed¶
Implementation Map¶
Priority 1: Core Foundation (Week 1)¶
- ✅ Set up Amplitude SDK
- ✅ Create
AnalyticsServicewrapper - ✅ Implement user identification & properties
- ✅ Wire up messaging events (80% of volume)
Priority 2: Engagement (Week 1-2)¶
- ✅ Implement conversation lifecycle events
- ✅ Add group thread tracking
- ✅ Track relationship progression
Priority 3: Features (Week 2)¶
- ✅ Superpower events (trigger, result, accept/reject)
- ✅ OAuth integration events
- ✅ Memory system events
Priority 4: Performance & Cost (Week 2-3)¶
- ✅ LLM usage tracking (token, cost, latency)
- ✅ Reliability events (errors, failures)
Priority 5: Future¶
- ⏳ Billing events (when Stripe launches)
- ⏳ iOS/Android app events (when apps launch)
- ⏳ Web app events (when web interface launches)
Key Metrics & Dashboards¶
Activation Funnel¶
user_discovered (100%)
↓
phone_verification_completed (85%)
↓
persona_selected (80%)
↓
First conversation_started (75%)
↓
First superpower_triggered (40%)
↓
First integration_connected (25%)
Engagement Metrics¶
- DAU/WAU/MAU: Unique users with
message_sent_userper day/week/month - Messages per Active User: Avg
message_sent_usercount per DAU - Session Depth: Avg messages per
conversation_startedevent - 1:1 vs Group: % of messages with
is_group_thread=true
Superpower Adoption¶
- Enablement Rate: % users with ≥1
superpower_enabled - Trigger Frequency:
superpower_triggeredper user per day - Acceptance Rate:
superpower_accepted/superpower_triggered - OAuth Conversion: % users completing
integration_connectedafter superpower triggers
LLM Economics¶
- Cost per User: Sum(
llm_cost_usd) / DAU - Cost per Message: Sum(
llm_cost_usd) / Count(message_sent_companion) - Token Efficiency: Avg
total_tokensperprompt_type - Model Mix: Distribution of
modelinllm_request_completed
Retention Cohorts¶
- D1/D7/D30 Retention: % users returning after signup
- Superpower Impact: Retention uplift for users with ≥1 enabled superpower
- Relationship Impact: Retention by
relationship_stage
Revenue (Future)¶
- MRR: Sum of
subscription_activatedmonthly charges - ARPU: MRR / Active Users
- LTV:CAC: Lifetime Value : Customer Acquisition Cost
- Gross Margin: Revenue - LLM Costs per cohort
Privacy & Compliance¶
Data We NEVER Track¶
❌ Message text content ❌ Email subjects or bodies ❌ Calendar event titles ❌ Names (except persona names) ❌ Specific locations ❌ OAuth tokens ❌ Phone numbers (only hashed for correlation)
Data We DO Track¶
✅ Message counts and lengths ✅ Event timestamps ✅ Feature usage (which workflows) ✅ Aggregate metrics (# of calendar events) ✅ Performance data (latency, tokens) ✅ Error types (no sensitive details)
Amplitude Data Retention¶
- 30 days: Raw event data
- 5 years: Aggregated metrics
- User deletion: Supports GDPR right to be forgotten
Testing & Validation¶
Manual Testing Checklist¶
- Send test message → verify
message_sent_userappears in Amplitude - Trigger workflow → verify
superpower_triggered+superpower_result_sent - Complete OAuth → verify
integration_connected - Send 10 messages → verify
total_messages_sentuser property updates - Advance relationship stage → verify
relationship_stage_changed
Automated Tests¶
- Unit tests for
AnalyticsServicemethods - Integration tests for each event type
- Mock Amplitude client for CI/CD
- Load testing (10,000 events/min)
Validation Queries (Amplitude)¶
-- Daily Active Users
SELECT DATE(timestamp), COUNT(DISTINCT user_id)
FROM events
WHERE event_type = 'message_sent_user'
GROUP BY DATE(timestamp)
-- Superpower Acceptance Rate
SELECT superpower_id,
COUNT(CASE WHEN event_type = 'superpower_accepted' THEN 1 END) /
COUNT(CASE WHEN event_type = 'superpower_triggered' THEN 1 END) as acceptance_rate
FROM events
WHERE event_type IN ('superpower_triggered', 'superpower_accepted')
GROUP BY superpower_id
-- LLM Cost per User
SELECT user_id,
SUM(llm_cost_usd) as total_cost,
COUNT(*) as total_requests,
AVG(latency_ms) as avg_latency
FROM events
WHERE event_type = 'llm_request_completed'
GROUP BY user_id
Version History¶
- v1.0 (2025-11-06): Initial tracking sheet based on spec + codebase exploration
- v1.1 (TBD): Add billing events when Stripe launches
- v1.2 (TBD): Add mobile app events when iOS/Android launch
Document Owner: Engineering Team Last Updated: 2025-11-06 Status: Ready for Implementation ✅