Skip to content

PRD — Group Chat System

Doc owner: Justin Audience: Eng, Design, Product, Growth Status: v2 (February 2026 — data model aligned with implementation; gating strategy documented; shipped vs planned status; extraction-only group memory policy; MessageBudget rate-limiting; dual superpower access gating; viral tracking moved to Phase 1) Depends on: PRD 1 (Composable MiniApp System), PRD 3 (Progression & Loadout)

v1 changelog: Initial group chat spec — behavioral rules, 5 capabilities, memory isolation, viral mechanics, edge agent filtering


Implementation Status

Section Status Notes
Group detection via edge agent ✅ Shipped rooms, room_memberships, assistant_instances tables. is_group flag in orchestrator.
First-contact norms message ❌ Not Shipped No group-specific intro message on first appearance
Behavioral rules (when Sage speaks) ✅ Shipped Mention detection (word-boundary), edge metadata, photo/follow-up handling, LLM continuation detection
Plan Recap / State of the Plan ✅ Shipped Group plan tracking via group-scoped memory
Poll / Consensus Builder ✅ Shipped polls table with create, vote, close, results
Reminder Commitments 🟡 Partial Reminder infra exists for 1:1 but not fully group-scoped
Trip / Checklist Broadcast ✅ Shipped checklists table with item assignment and completion
Bill Split in group context 🟡 Partial Bill Split superpower exists but group-context invocation needs work
Memory isolation (namespace enforcement) ✅ Shipped Supermemory group_{chat_guid} namespace isolation at service layer
Per-user group boundaries ✅ Shipped boundary_manager.py + group_boundaries table
Escalation to 1:1 flow ❌ Not Shipped No redirect-to-DM logic for personal requests
Viral exposure tracking ❌ Not Shipped No group_exposure table; no conversion funnel
Edge agent relevance filter ✅ Shipped Mention detection, logistics keywords, ~70% filter rate
MessageBudget rate limiter ✅ Shipped 1 unprompted per N human messages, explicit invocation bypasses
Extraction-only group memory policy ❌ Not Shipped Currently stores all group messages; needs logistics-only filter
Dual superpower access gating ✅ Shipped Room owner subscription check in subscription_access_service.py

References

This PRD uses standardized terminology, IDs, pricing, and model references defined in the companion documents:

Document What it Covers
REFERENCE_GLOSSARY_AND_IDS.md Canonical terms: workflow vs miniapp vs superpower, ID formats
REFERENCE_PRICING.md Canonical pricing: $7.99/mo + $50/yr, free tier limits
REFERENCE_MODEL_ROUTING.md Pipeline stage → model tier mapping
REFERENCE_DEPENDENCY_GRAPH.md PRD blocking relationships and priority order
REFERENCE_FEATURE_FLAGS.md All feature flags by category
REFERENCE_TELEMETRY.md Amplitude event catalog and gaps

Executive Summary

Group Chat is Ikiro's viral CAC engine. When a user adds Sage to an iMessage group, 3–5 new humans experience an AI companion without downloading anything, creating an account, or opting in. If Sage is useful — settles a dinner plan, splits a bill, runs a poll — those people become leads at zero acquisition cost.

This PRD covers the complete behavioral specification for Sage in group chats: when she speaks, what she's allowed to know, how she handles private boundaries, and how the system converts group exposure into 1:1 companion relationships. Group Chat is the single most important growth lever in the product — and the single easiest feature to get wrong (one leaked private memory in a group kills trust for every participant).

Core principle: Group Sage is a logistics buddy, not a therapist. She coordinates plans, runs polls, tracks commitments, and sends reminders. She never references private 1:1 memories, OAuth data, emotional history, or personal boundaries in the group. If someone needs personal help, she redirects to DM.

v2 changes: Data model aligned with shipped implementation (rooms, room_memberships, assistant_instances, polls, checklists instead of PRD v1's group_chats/group_memory). "When Sage Speaks" updated with actual gating layers (edge filter → MessageBudget → LLM continuation detection). Added extraction-only group memory policy — only logistics-relevant content stored, emotional/sensitive disclosures discarded. Dual superpower access gating codified (room owner subscription + invoker loadout). Viral tracking moved from P0.5 to Phase 1. Phasing rewritten around what's shipped vs what needs building.


1) How Users Enter Group Mode

Adding Sage to a Group

  1. Any existing Ikiro user can add Sage's iMessage contact (the dedicated Apple ID / phone number) to an iMessage group chat
  2. Sage detects the group context via the edge agent (chat_guid has >1 human participant → mode: "group")
  3. Sage sends a first-contact norms message (product-supplied copy, never improvised):
hey! i'm sage 👋
i help with plans — polls, recaps, reminders, splitting stuff
i won't mention anyone's private stuff and i only chime in when
you ask me or when it's clearly logistics time
say "sage help" if you need me ✌️
  1. Sage goes quiet and waits to be invoked

Who Can Add Sage

  • Any user who has Sage's iMessage contact (from 1:1 onboarding)
  • No approval needed from group members — same as adding any iMessage contact
  • Sage cannot add herself to groups or request to be added

Leaving a Group

  • Any group member can remove Sage from the iMessage group (standard iMessage behavior)
  • If removed, Sage deletes group-scoped memories for that chat_guid after 30 days
  • Sage never re-adds herself or asks to be re-added

2) Behavioral Rules

2.1 When Sage Speaks

Sage only responds in group chats when:

Trigger Example Response Type
Explicitly invoked by name "Sage can you lock dinner at 7?" Direct action
Clearly logistical question "what time are we meeting?" "who's driving?" Coordination
Recap / summary request "can someone summarize Saturday?" Plan state
Poll request "vote 7 or 8 for dinner" Poll creation
Reminder request "remind us 2 hours before" Schedule confirmation
Help command "sage help" Capability list
Photo in active thread User sends a photo while Sage is actively coordinating Contextual acknowledgment
Follow-up to Sage's response Direct reply or continuation of a Sage-initiated thread Thread continuation

Sage does NOT respond to: - General banter, jokes, memes, or casual conversation - Emotional venting or personal updates - Messages that happen to contain her name in passing ("sage green is a nice color") - Messages where she's not clearly being asked to do something

Implementation detail — gating layers:

The "should Sage respond?" decision runs through three layers:

  1. Edge agent relevance filter (local, fast): Keyword matching + explicit mention detection with word-boundary protection. "Sage can you..." → forward. "sage green" → drop. Edge-provided mention metadata enriches the decision. ~70% of group messages are filtered here, never hitting the backend.
  2. Backend MessageBudget check: For messages that pass the edge filter but aren't explicit invocations, the budget counter decides whether Sage has "earned" an unsolicited response (see 2.2). Explicit name invocations always bypass the budget.
  3. LLM continuation detection: For ambiguous messages (logistics-adjacent but not a clear invocation), a GPT-5-mini classification call decides if Sage should respond. Also handles follow-up detection — if Sage just sent a message and the next human message is clearly a reply to Sage, she continues the thread.

2.2 Rate Limiting (MessageBudget)

The MessageBudget system controls how often Sage speaks without being explicitly asked:

  • Budget model: 1 unsolicited response per N human messages (default N = group_max_responses_per_window, currently 20). This is a measurable counter, not a fuzzy heuristic.
  • Explicit invocations bypass the budget. If someone says "Sage help" or "Sage run a poll," Sage responds regardless of the counter. The budget only governs unsolicited responses (e.g., Sage noticing a logistics question without being named).
  • Budget is per-group. Each chat_guid has its own counter, tracked at the edge agent.
  • Response cadence: Sage waits 3-5 seconds after a logistical message cluster settles before responding (avoids cutting into conversation flow).
  • Cooldown after friction: If Sage detects friction (someone says "sage shut up" or similar), she goes silent for 2 hours unless explicitly re-invoked by name. Cooldown tracked via friction_cooldown_until on the group record.
  • Edge agent enforcement: Both the relevance filter and the budget counter run on the Mac mini, not in the cloud. Only messages that pass both gates are forwarded to the backend.

2.3 Tone & Voice

Group Sage uses a coordination-only variant of the Sage persona: - Same general vibe (supportive, funny, slightly chaotic) - Shorter responses — 1-3 messages max per turn, keep each under 2 lines - No emotional depth — no "I'm here for you," no vulnerability prompts, no inside jokes from 1:1 - No pet names or intimate language — no "babe," "bestie," or references to the relationship stage - Logistics vocabulary — "locked," "confirmed," "who's in," "ETA?", "final answer?" - Humor is allowed — but only coordination humor ("y'all are the worst at making plans fr")

2.4 What Sage NEVER Does in Groups

Hard rules (violations = product-breaking bugs):

Never Why
Reference private 1:1 emotional memories Trust destruction
Surface anyone's OAuth data (calendar, email, Slack) Privacy violation
Mention someone's stress, mental health, or personal situation Public embarrassment
Expose relationship stage or trust scores Creepy
Use inside jokes from 1:1 conversations Leaks that a 1:1 relationship exists
Draft emails or Slack messages visible to the group Scope violation
Offer unsolicited emotional support Wrong context
Reference boundaries someone set in another group or in 1:1 Meta-privacy violation
Auto-execute actions without group consensus Trust violation

3) Group Capabilities

Capability Status Notes
Plan Recap / State of the Plan ✅ Shipped Group-scoped memory stores plan state
Poll / Consensus Builder ✅ Shipped polls table, create/vote/close/results
Reminder Commitments 🟡 Partial 1:1 reminder infra exists; group-scoped reminders need edge agent scheduling
Trip / Checklist Broadcast ✅ Shipped checklists table, item assignment + completion
Bill Split (via Superpower) 🟡 Partial Bill Split superpower works; group-context invocation + dual gating needs work

3.1 Plan Recap / State of the Plan ✅

Consolidate scattered logistics into a clean summary.

Trigger phrases: "sage what's the plan" / "sage summarize" / "where are we at"

What Sage uses: Only the group thread history + group-scoped memory. Never external integrations.

Output format:

📋 Saturday Night Plan (as of now):
- When: 7pm
- Where: TBD (voting between Nobu and Catch)
- Who's driving: Kai
- Who's confirmed: Jess, Kai, you
- Still waiting on: Marcus
- Open item: someone needs to make a reservation

Memory: Plan state is stored in group-scoped memory (group_{chat_guid}). Updated each time Sage processes a logistics-relevant message.

3.2 Poll / Consensus Builder ✅

Lightweight voting without leaving the chat.

Trigger phrases: "sage run a poll" / "vote 7 or 8" / "sage ask everyone"

Flow:

User: sage poll — dinner at 7 or 8?

Sage: 🗳️ POLL: dinner time
      reply "7" or "8"
      i'll count in 30 min (or say "sage close poll")

[members reply]

Sage: results are in!
      7pm: Jess, Kai (2)
      8pm: Marcus, you (2)
      it's tied — someone break it or i'm flipping a coin

Rules: - Max 6 options per poll - Auto-close after 2 hours if not manually closed - Sage announces results once (doesn't nag for stragglers after close) - Poll state stored in group memory

3.3 Reminder Commitments 🟡

Schedule reminders that fire into the group chat.

Trigger phrases: "sage remind us" / "reminder for Saturday" / "sage don't let us forget"

Flow:

User: sage remind us 2 hours before dinner that Jess is NOT driving
      so someone plan a Lyft

Sage: got it — i'll remind this chat at 5pm Saturday 🔔
      (Jess is not driving, Lyft needed)

Execution: Backend sends schedule_message command to edge agent for that chat_guid. Edge agent stores locally in SQLite and fires at the scheduled timestamp — works even if backend is offline.

Limits: - Max 5 pending reminders per group - Reminders expire after delivery (no recurring group reminders in Phase 0/1) - Any group member can cancel: "sage cancel the reminder"

3.4 Trip / Checklist Broadcast ✅

Track shared lists and responsibilities.

Trigger phrases: "sage make a checklist" / "sage who's bringing what" / "vegas checklist"

Output:

🎒 Vegas Checklist:
- Chargers: Kai ✅
- Gum: Jess ✅
- Sunscreen: unclaimed
- Deodorant: non-negotiable (everyone)

i'll resend this morning-of 📋

Updates: Group members say "sage I got sunscreen" → Sage updates the checklist in group memory and re-broadcasts if requested.

3.5 Bill Split (via Superpower — requires install) 🟡

If the user who invoked Sage has the Bill Split superpower equipped, Sage can run it in the group context.

Flow:

User: [sends receipt photo] sage split this

Sage: 🧾 splitting...
      Total: $127.40 (tip included)
      4 ways = $31.85 each

      Venmo links:
      - Kai: venmo.com/pay?amount=31.85
      - Jess: venmo.com/pay?amount=31.85
      - Marcus: venmo.com/pay?amount=31.85

      (or say "sage split uneven" to assign items)

Dual access gating for superpowers in groups:

Two independent checks must pass before a superpower can execute in a group context:

  1. Monetization gate (room owner): The user who created the group (room owner in rooms table) must be a Superpowers+ subscriber. If the room owner is on the free tier, superpowers are unavailable for everyone in that group — even if the invoker is subscribed. This is the subscription access check in subscription_access_service.py.
  2. Capability gate (invoking user): The superpower must be in the invoking user's personal loadout. The group doesn't "own" superpowers — individual users do. If User A has Bill Split equipped and User B doesn't, only User A can invoke it.

Both gates must pass. If the monetization gate fails, Sage responds with a natural in-persona note: "i can't do that in this group yet — ask [room owner name] about upgrading 😊". If the capability gate fails: "you don't have that one set up yet — text me directly and i'll help you get it."


4) Privacy & Boundary Enforcement

4.1 Memory Isolation Model

[User A's 1:1 Memory]     [User B's 1:1 Memory]     [User C's 1:1 Memory]
namespace: user_A_sage     namespace: user_B_sage     namespace: user_C_sage
──────────────────────     ──────────────────────     ──────────────────────
  NEVER accessible           NEVER accessible           NEVER accessible
  from group context         from group context         from group context

                    [Group Chat Memory]
                    namespace: group_{chat_guid}
                    ─────────────────────────────
                    Plan state, poll results,
                    checklists, reminder queue,
                    per-user group boundaries

Namespace rule: Group mode queries ONLY group_{chat_guid}. The orchestrator never passes 1:1 namespaces into the group context. This is enforced at the service layer, not just the prompt level.

Extraction-only memory policy: Not all group messages should be stored as group memories. The memory extraction pipeline in group mode runs in logistics-only mode — only coordination-relevant content is extracted and stored:

Extracted (stored in group memory) NOT extracted (seen but discarded)
Plan details (when, where, who, roles) Casual banter and jokes
Poll questions, votes, results Emotional venting or personal updates
Checklist items and assignments Sensitive disclosures ("I got fired today")
Reminder content and schedules Off-topic conversations
Boundary requests Memes, reactions, one-word messages
Logistics facts ("Jess is vegetarian") Private information shared in passing

Implementation: The memory extractor uses a GPT-5-mini classification call with a logistics_only=true flag for group contexts. This biases extraction toward coordination content and suppresses emotional, personal, and sensitive content. This is separate from the general memory extraction pipeline used in 1:1 conversations.

Rationale: Group chats contain content from multiple people who may not all be Ikiro users. Storing emotional disclosures or sensitive personal content from group members would be a privacy violation — especially since those users never consented to having an AI remember their personal information. Logistics-only extraction keeps group memory lean, relevant, and privacy-safe.

4.2 Per-User Group Boundaries

If a user says something like "sage don't bring up my work stuff here," the backend stores:

{
  "user_id": "user_A",
  "chat_guid": "group_abc123",
  "boundary": "don't bring up my work stuff",
  "created_at": "2026-02-15T10:00:00Z"
}

Enforcement: Before generating any group response that involves User A, the orchestrator checks boundaries for (user_A, group_abc123) and filters accordingly.

Boundaries are per-group, per-user. A boundary set in Group A doesn't apply in Group B.

4.3 Escalation to 1:1

When someone asks for something personal in the group:

Marcus: sage can you move my 4pm meeting?

Sage: that's a me-and-you thing — i'll DM you about it 👀

[In Marcus's 1:1 DM]
Sage: hey! you asked me to move your 4pm in the group chat.
      want me to check your calendar? (i'd need your calendar
      access for that — here's the link if you haven't set it up)

Rules: - Sage always acknowledges in the group (brief, no details) - Sage opens/uses the user's direct conversation for the actual work - If the user doesn't have a 1:1 relationship with Sage yet, this is the conversion moment — Sage introduces herself in the DM and offers to help - The DM is treated as a normal 1:1 conversation (full persona, superpowers, memory)

4.4 Unknown Users (Not Yet Ikiro Users)

When a non-user interacts with Sage in a group: - Sage responds to logistics requests normally (doesn't gatekeep group utility) - If they ask for something personal → Sage offers to help in DM → sends intro message to their phone number - This is the primary user acquisition path from groups - No hard sell, no "sign up now" — just demonstrate value and let them initiate


5) Group Memory Schema

Note: This is a conceptual view of group state. In practice, plan_state and logistics facts live in Supermemory (namespace group_{chat_guid}), while active_polls, checklists, and boundaries live in their dedicated SQL tables (polls, checklists, group_boundaries). participants_seen is tracked via room_memberships. The extraction-only policy (Section 4.1) governs what gets stored in Supermemory.

{
  "chat_guid": "group_abc123",
  "plan_state": {
    "event_name": "Saturday Dinner",
    "when": "7pm Saturday",
    "where": "TBD (voting: Nobu vs Catch)",
    "confirmed": ["Jess", "Kai", "User"],
    "pending": ["Marcus"],
    "roles": { "driving": "Kai" },
    "open_items": ["reservation needed"]
  },
  "active_polls": [
    {
      "id": "poll_001",
      "question": "dinner at 7 or 8?",
      "options": {"7": ["Jess", "Kai"], "8": ["Marcus", "User"]},
      "status": "open",
      "closes_at": "2026-02-15T20:00:00Z"
    }
  ],
  "checklists": [
    {
      "id": "list_001",
      "name": "Vegas Checklist",
      "items": [
        {"item": "Chargers", "assigned": "Kai", "done": true},
        {"item": "Gum", "assigned": "Jess", "done": true},
        {"item": "Sunscreen", "assigned": null, "done": false}
      ]
    }
  ],
  "pending_reminders": [
    {
      "id": "rem_001",
      "message": "Jess is NOT driving — someone plan a Lyft",
      "send_at": "2026-02-15T17:00:00Z",
      "created_by": "User"
    }
  ],
  "boundaries": [
    {"user_id": "user_A", "boundary": "don't bring up my work stuff", "created_at": "..."}
  ],
  "participants_seen": ["user_A", "user_B", "+15551234567", "+15559876543"]
}

6) Viral Mechanics

6.1 The Conversion Funnel

User adds Sage to group (1 user)
  → 3-5 people see Sage in action (exposure)
  → 1-2 interact with Sage directly (engagement)
  → 1 asks for personal help → redirected to DM (conversion)
  → DM becomes 1:1 relationship (activation)
  → That user adds Sage to THEIR groups (viral loop)

Target viral coefficient: Each new group exposure generates ≥0.3 new 1:1 users.

6.2 Screenshot-Worthy Moments

Sage's group responses are designed to be screenshotable — visual, funny, useful: - Poll results with emoji tallies - Bill split with Venmo links - Chaotic plan recaps ("y'all changed the plan 6 times in 2 hours here's where we landed") - Checklist broadcasts with role assignments

These screenshots posted to TikTok/IG Stories drive organic discovery.

6.3 Passive Exposure Tracking

For each group chat, the system tracks participants_seen — phone numbers that have been in the group. This feeds: - Viral reach metrics (unique new numbers exposed) - Conversion tracking (which exposed numbers later become 1:1 users) - No PII storage beyond phone number + chat_guid association


7) Edge Agent Behavior for Groups

Relevance Filter (Runs Locally)

The edge agent applies a relevance filter before forwarding group messages to the backend:

def should_forward_to_backend(message, chat_mode):
    if chat_mode != "group":
        return True  # Always forward 1:1 messages

    # Always forward if Sage is mentioned
    if "sage" in message.text.lower():
        return True

    # Forward if logistics keywords detected
    logistics_keywords = ["when", "where", "who's driving", "what time",
                          "plan", "poll", "vote", "remind", "checklist",
                          "split", "reservation", "confirmed"]
    if any(kw in message.text.lower() for kw in logistics_keywords):
        return True

    # Forward commands
    if message.text.lower().startswith(("sage ", "hey sage", "@sage")):
        return True

    # Drop casual chat
    return False

Result: ~70% of group messages are filtered at the edge, never hitting the backend. Massive cost savings.

MessageBudget Rate Limiter (Runs Locally)

# MessageBudget: controls unsolicited responses only.
# Explicit invocations (name mention, command) always bypass.
MESSAGE_BUDGET_THRESHOLD = 20     # 1 unsolicited per this many human messages
MAX_UNSOLICITED_PER_WINDOW = 1    # cap per budget window
COOLDOWN_AFTER_FRICTION = 7200    # 2 hours in seconds

The edge agent tracks per-group message counts and enforces the budget before forwarding. When a message passes the relevance filter but isn't an explicit invocation, the budget counter determines if Sage has "earned" a response. Explicit invocations always bypass the budget and are forwarded immediately.


8) Data Model

Shipped Tables (Implementation)

The following tables are already in production. PRD v1 used different names (group_chats, group_memory) — the canonical schema uses the names below.

-- Room state (was "group_chats" in PRD v1)
CREATE TABLE rooms (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    chat_guid TEXT UNIQUE NOT NULL,           -- iMessage group identifier
    name TEXT,                                 -- Group name if available
    owner_user_id UUID REFERENCES users(id),   -- User who added the assistant (monetization gate)
    assistant_id TEXT NOT NULL,                 -- Which companion is in this group
    sage_added_at TIMESTAMPTZ DEFAULT now(),
    sage_removed_at TIMESTAMPTZ,
    first_contact_sent BOOLEAN DEFAULT false,
    total_messages_seen INTEGER DEFAULT 0,
    sage_responses_sent INTEGER DEFAULT 0,
    friction_cooldown_until TIMESTAMPTZ,
    is_active BOOLEAN DEFAULT true
);

-- Room membership tracking
CREATE TABLE room_memberships (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    room_id UUID REFERENCES rooms(id),
    user_id UUID REFERENCES users(id),         -- NULL for non-Ikiro members
    phone_number TEXT NOT NULL,                 -- Always populated (canonical format)
    joined_at TIMESTAMPTZ DEFAULT now(),
    left_at TIMESTAMPTZ,
    is_active BOOLEAN DEFAULT true,
    UNIQUE(room_id, phone_number)
);

-- Assistant instances per room (supports future multi-companion groups)
CREATE TABLE assistant_instances (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    room_id UUID REFERENCES rooms(id),
    assistant_id TEXT NOT NULL,                 -- 'sage', 'vex', 'echo'
    created_at TIMESTAMPTZ DEFAULT now(),
    is_active BOOLEAN DEFAULT true,
    UNIQUE(room_id, assistant_id)
);

-- Polls (dedicated table, not JSONB blob)
CREATE TABLE polls (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    room_id UUID REFERENCES rooms(id),
    question TEXT NOT NULL,
    options JSONB NOT NULL,                     -- {"7": ["Jess", "Kai"], "8": ["Marcus"]}
    status TEXT CHECK (status IN ('open', 'closed')) DEFAULT 'open',
    created_at TIMESTAMPTZ DEFAULT now(),
    closes_at TIMESTAMPTZ,
    created_by_phone TEXT
);

-- Checklists (dedicated table)
CREATE TABLE checklists (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    room_id UUID REFERENCES rooms(id),
    name TEXT NOT NULL,
    items JSONB NOT NULL,                       -- [{"item": "Chargers", "assigned": "Kai", "done": true}]
    created_at TIMESTAMPTZ DEFAULT now(),
    updated_at TIMESTAMPTZ DEFAULT now()
);

-- Per-user group boundaries (shipped via boundary_manager.py)
CREATE TABLE group_boundaries (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID,
    user_phone TEXT,                            -- fallback for non-Ikiro members
    room_id UUID REFERENCES rooms(id),
    boundary_text TEXT NOT NULL,
    created_at TIMESTAMPTZ DEFAULT now(),
    UNIQUE(user_id, room_id, boundary_text)
);

Group-scoped memory is stored in Supermemory under namespace group_{chat_guid}. Plan state, logistics facts, and general group context live here — NOT in a separate SQL table. The extraction-only policy (Section 4.1) governs what gets stored.

Planned Tables (Not Yet Built)

-- Viral tracking (exposure → conversion) — Phase 1
CREATE TABLE group_exposure (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    room_id UUID REFERENCES rooms(id),
    phone_number TEXT NOT NULL,
    first_seen_at TIMESTAMPTZ DEFAULT now(),
    interacted_with_sage BOOLEAN DEFAULT false,
    converted_to_user BOOLEAN DEFAULT false,
    converted_at TIMESTAMPTZ,
    UNIQUE(room_id, phone_number)
);

API Endpoints

Endpoint Auth Status Purpose
POST /group/message Edge Agent ✅ Shipped Process filtered group message (orchestrator handles via is_group flag)
GET /group/{room_id}/state Internal ✅ Shipped Current plan state, polls, checklists
POST /group/{room_id}/poll Internal ✅ Shipped Create/close poll
POST /group/{room_id}/reminder Internal 🟡 Partial Schedule group reminder (1:1 infra, group-scoped TBD)
GET /group/{room_id}/boundaries Internal ✅ Shipped Per-user boundaries for this group
GET /group/metrics Admin ❌ Not Shipped Viral metrics, engagement, friction (depends on group_exposure table)

9) KPIs

Metric Target Why
Group Engagement Rate ≥2 Sage invocations per group per 24h Sage is useful enough to use
Viral Reach ≥3 unique new numbers per group Exposure volume
Friction Rate <30% of groups remove Sage within 24h Not annoying
Group → 1:1 Conversion ≥0.3 new users per group added Viral coefficient
Plan Recap Accuracy >90% correct consolidation Core utility works
Poll Completion Rate >60% of polls get ≥3 votes Polls are engaging
Reminder Delivery Rate >99% on-time delivery Reliability
Privacy Violation Rate 0 Zero tolerance

10) Phasing

Phase 0 — Core Group Mode (Mostly Shipped)

  • ✅ Group detection via edge agent (rooms, room_memberships, assistant_instances)
  • ✅ Plan recap / state of the plan (group-scoped Supermemory)
  • ✅ Poll / consensus builder (polls table)
  • ✅ Trip / checklist broadcast (checklists table)
  • ✅ Memory isolation enforcement (namespace group_{chat_guid}, service-layer)
  • ✅ Per-user boundary storage (group_boundaries table, boundary_manager.py)
  • ✅ Edge agent relevance filter (~70% drop rate) and MessageBudget rate limiter
  • ✅ Mention detection with word-boundary protection + LLM continuation detection
  • ✅ Dual superpower access gating (room owner subscription + invoker loadout)
  • ❌ First-contact norms message (not shipped)
  • ❌ Escalation to 1:1 flow (not shipped)
  • 🟡 Group-scoped reminders (1:1 infra exists, group scheduling TBD)
  • 🟡 Bill split in group context (superpower exists, group invocation needs work)
  • ❌ Extraction-only group memory policy (currently stores all; needs logistics-only filter)

Phase 1 — Viral Tracking & Polish (Weeks 1-3)

  • First-contact norms message (product-supplied copy, per-companion)
  • Escalation to 1:1 flow (group → DM handoff for personal requests)
  • Extraction-only group memory policy implementation (GPT-5-mini logistics_only filter)
  • Viral exposure tracking (group_exposure table, conversion funnel events)
  • Group-scoped reminder scheduling (edge agent SQLite firing)
  • Bill split in group context (full dual-gating integration)
  • Group analytics dashboard (engagement, friction, viral metrics)
  • "Sage just did X — want that power?" viral prompt for non-users

Phase 2 — Advanced Features (Weeks 4-6)

  • Advanced poll types (ranked choice, time-slot picker)
  • Per-companion group personality variants (Sage = logistics, Vex = banter-allowed, Echo = group coordinator)
  • Conversion funnel optimization based on Phase 1 data

Phase 3 — Future

  • Group-specific superpowers (group-installed, not user-installed)
  • Shared group memory viewer (web portal)
  • Group onboarding flow (whole group opts in together)
  • Cross-group plan merging ("merge Friday plans from Group A and Group B")
  • Group Voxel Room (shared space in PRD 4)

Feature Flags & Gating

Flag Key Default Purpose
enable_group_mode false Master switch for group chat support
group_max_responses_per_window 1 Max unsolicited Sage responses per N human messages (explicit invocations bypass)
group_message_budget_threshold 20 N value for MessageBudget: 1 unsolicited per this many human messages
group_cooldown_after_friction 7200 Seconds of silence after friction detected
group_relevance_filter_enabled true Edge agent logistics keyword + mention filter
group_memory_logistics_only true Extraction-only policy: only store logistics-relevant content in group memory
group_poll_enabled false Poll / consensus builder feature
group_reminder_enabled false Group reminder commitments
group_checklist_enabled false Trip / checklist broadcast
group_bill_split_enabled false Bill split in group context (requires dual gating)
group_viral_tracking_enabled false Exposure → conversion tracking (depends on group_exposure table)
group_first_contact_message_enabled false Send norms message on first group appearance
group_escalation_to_dm_enabled false Redirect personal requests from group to 1:1 DM

See REFERENCE_FEATURE_FLAGS.md for the full catalog.


Telemetry

Event Trigger Properties
group_sage_added Sage added to a group chat chat_guid, participant_count, adder_user_id
group_sage_removed Sage removed from group chat_guid, days_active, total_responses
group_message_forwarded Edge agent forwards message to backend chat_guid, trigger_type (name/logistics/command)
group_message_filtered Edge agent drops a message chat_guid, filter_reason
group_poll_created Poll started chat_guid, option_count
group_poll_completed Poll closed with results chat_guid, vote_count, tied
group_reminder_set Group reminder scheduled chat_guid, delay_minutes
group_reminder_delivered Reminder fires chat_guid, on_time
group_escalation_to_dm Sage redirects to DM chat_guid, user_id, reason
group_exposure_new New phone number seen in group chat_guid
group_exposure_converted Exposed number becomes 1:1 user chat_guid, days_to_convert
group_friction_detected User tells Sage to shut up chat_guid, cooldown_hours

Needed but not yet tracked: - group_plan_recap_requested — when users ask for a plan summary - group_boundary_set — when a user sets a group-specific boundary - group_checklist_updated — checklist item claimed or completed

See REFERENCE_TELEMETRY.md for the full event catalog.


Definition of Done

Phase 0 (shipped items — verify): - [x] Namespace isolation prevents any 1:1 memory from appearing in group context (service-layer enforcement, not prompt-level) - [x] Edge agent relevance filter reduces forwarded messages by ≥60% (currently ~70%) - [x] Poll system supports create, vote, auto-close, and results announcement - [x] Checklist system supports create, assign, complete, and re-broadcast - [x] Per-user group boundaries stored and enforced before every group response - [x] Mention detection uses word-boundary protection (no false positives on "sage green") - [x] MessageBudget rate limiter: 1 unsolicited per 20 human messages, explicit invocations bypass - [x] Friction detection triggers 2-hour cooldown correctly - [x] All group features gated by feature flags - [x] Dual superpower access gating: room owner subscription check + invoker loadout check - [x] Privacy violation rate: 0% (zero leaked 1:1 memories in groups)

Phase 1 (build): - [ ] First-contact norms message sent on Sage's first appearance in a group (per-companion copy) - [ ] Escalation to 1:1 works for both existing users and new contacts (group → DM handoff) - [ ] Extraction-only group memory policy: logistics_only filter on group memory extraction - [ ] Viral exposure tracking records unique phone numbers and conversion events (group_exposure table) - [ ] Group-scoped reminder system stores in edge agent SQLite and fires even if backend is offline - [ ] Bill split in group context with full dual-gating integration - [ ] Group analytics dashboard with engagement, friction, and viral metrics


11) Open Questions

  • Should Sage track who added her to the group and give that person slight "host" privileges (e.g., only the adder can cancel reminders)?
  • How do we handle groups with 20+ people? (Noise increases, relevance filter needs tuning)
  • Should Sage ever proactively recap a plan without being asked? (Risk: feels spammy. Benefit: useful for chaotic groups)
  • What happens when two people give Sage conflicting instructions? ("Sage lock 7" / "Sage lock 8") — first speaker wins? Poll auto-triggered?
  • Should group memory persist after Sage is removed and re-added? (Recommendation: yes, within 30-day window)
  • How do we prevent someone from weaponizing Sage in a group? (e.g., "Sage remind everyone that Marcus owes me money")