Files
vibn-frontend/COLLECTOR_HANDOFF_PERSISTENCE.md

9.5 KiB

Collector Handoff Persistence - Complete

Overview

The Collector AI now persists its checklist state to Firestore on every chat turn, ensuring the checklist survives across sessions and page refreshes.


What Was Added

1. Structured Output from AI

The Collector AI now returns both:

  • Conversational reply (user-facing message)
  • Collector handoff data (structured checklist state)
{
  "reply": "✅ I see you've uploaded 2 documents. Anything else?",
  "collectorHandoff": {
    "hasDocuments": true,
    "documentCount": 2,
    "githubConnected": false,
    "extensionLinked": false,
    "readyForExtraction": false
  }
}

2. Persistence to Firestore

Location: projects/{projectId}/phaseData.phaseHandoffs.collector

Structure:

interface CollectorPhaseHandoff {
  phase: 'collector';
  readyForNextPhase: boolean;       // Ready for extraction?
  confidence: number;               // 0.5 or 0.9
  confirmed: {
    hasDocuments?: boolean;         // Docs uploaded?
    documentCount?: number;         // How many?
    githubConnected?: boolean;      // GitHub connected?
    githubRepo?: string;            // Repo name
    extensionLinked?: boolean;      // Extension connected?
  };
  uncertain: {
    extensionDeclined?: boolean;    // User said no to extension?
    noGithubYet?: boolean;          // User doesn't have GitHub?
  };
  missing: string[];                // What's still needed
  questionsForUser: string[];       // Follow-up questions
  sourceEvidence: string[];         // Source references
  version: string;                  // "1.0"
  timestamp: string;                // ISO timestamp
}

3. Code Changes

app/api/ai/chat/route.ts

Added structured output schema:

const ChatReplySchema = z.object({
  reply: z.string(),
  collectorHandoff: z.object({
    hasDocuments: z.boolean().optional(),
    documentCount: z.number().optional(),
    githubConnected: z.boolean().optional(),
    githubRepo: z.string().optional(),
    extensionLinked: z.boolean().optional(),
    extensionDeclined: z.boolean().optional(),
    noGithubYet: z.boolean().optional(),
    readyForExtraction: z.boolean().optional(),
  }).optional(),
});

Added persistence logic:

// If in collector mode and AI provided handoff data, persist it
if (resolvedMode === 'collector_mode' && reply.collectorHandoff) {
  const handoff: CollectorPhaseHandoff = {
    phase: 'collector',
    readyForNextPhase: reply.collectorHandoff.readyForExtraction ?? false,
    confidence: reply.collectorHandoff.readyForExtraction ? 0.9 : 0.5,
    confirmed: {
      hasDocuments: reply.collectorHandoff.hasDocuments,
      documentCount: reply.collectorHandoff.documentCount,
      githubConnected: reply.collectorHandoff.githubConnected,
      githubRepo: reply.collectorHandoff.githubRepo,
      extensionLinked: reply.collectorHandoff.extensionLinked,
    },
    uncertain: {
      extensionDeclined: reply.collectorHandoff.extensionDeclined,
      noGithubYet: reply.collectorHandoff.noGithubYet,
    },
    missing: [],
    questionsForUser: [],
    sourceEvidence: [],
    version: '1.0',
    timestamp: new Date().toISOString(),
  };

  // Persist to Firestore
  await adminDb.collection('projects').doc(projectId).set(
    {
      'phaseData.phaseHandoffs.collector': handoff,
    },
    { merge: true }
  );
}

Added console logging:

console.log(`[AI Chat] Collector handoff persisted:`, {
  hasDocuments: handoff.confirmed.hasDocuments,
  githubConnected: handoff.confirmed.githubConnected,
  extensionLinked: handoff.confirmed.extensionLinked,
  readyForExtraction: handoff.readyForNextPhase,
});

lib/ai/prompts/collector.ts

Added structured output instructions:

**STRUCTURED OUTPUT:**
In addition to your conversational reply, you MUST also return a collectorHandoff object tracking the checklist state:

```json
{
  "reply": "Your conversational response here",
  "collectorHandoff": {
    "hasDocuments": true,              // Are documents uploaded?
    "documentCount": 5,                // How many?
    "githubConnected": true,           // Is GitHub connected?
    "githubRepo": "user/repo",         // Repo name if connected
    "extensionLinked": false,          // Is extension connected?
    "extensionDeclined": false,        // Did user say no to extension?
    "noGithubYet": false,              // Did user say they don't have GitHub yet?
    "readyForExtraction": false        // Is user ready to move to extraction? (true when they say "yes" to "Is that everything?")
  }
}

Update this object on EVERY response based on the current state of:

  • What you see in projectContext (documents, GitHub, extension)
  • What the user explicitly confirms or declines

This data will be persisted to Firestore so the checklist state survives across sessions.


---

## How It Works

### **Flow:**

1. **User sends message** → "I uploaded some docs"

2. **Collector AI analyzes** `projectContext`:
   - Sees `knowledgeSummary.bySourceType.imported_document = 3`
   - Sees `project.githubRepo = null`
   - Sees no extension data

3. **AI responds with structured output**:
   ```json
   {
     "reply": "✅ I see you've uploaded 3 documents. Do you have a GitHub repo?",
     "collectorHandoff": {
       "hasDocuments": true,
       "documentCount": 3,
       "githubConnected": false,
       "extensionLinked": false,
       "readyForExtraction": false
     }
   }
  1. Backend persists handoff to Firestore:

    • Writes to projects/{projectId}/phaseData.phaseHandoffs.collector
    • Logs checklist state to console
  2. On next page load/refresh:

    • Checklist state is still there
    • AI can see previous state and continue from where it left off

Benefits

Checklist Survives Sessions

  • User can close browser and come back
  • Progress is never lost

Debugging & Analytics

  • Can see exact checklist state at any point
  • Helps debug "why did AI ask that?" questions

Smart Handoff Protocol

  • When readyForExtraction = true, system knows to transition
  • Can build automatic phase transitions later

Historical Tracking

  • Timestamp on every update
  • Can see how checklist evolved over time

Example Firestore Document

{
  "projects": {
    "abc123": {
      "name": "My SaaS",
      "currentPhase": "collector",
      "phaseData": {
        "phaseHandoffs": {
          "collector": {
            "phase": "collector",
            "readyForNextPhase": false,
            "confidence": 0.5,
            "confirmed": {
              "hasDocuments": true,
              "documentCount": 3,
              "githubConnected": true,
              "githubRepo": "user/my-saas",
              "extensionLinked": false
            },
            "uncertain": {
              "extensionDeclined": false,
              "noGithubYet": false
            },
            "missing": [],
            "questionsForUser": [],
            "sourceEvidence": [],
            "version": "1.0",
            "timestamp": "2025-11-17T22:30:00.000Z"
          }
        }
      }
    }
  }
}

Testing

Manual Test:

  1. Start a new project chat
  2. Say: "I uploaded some documents"
  3. Check Firestore:
    # In Firebase Console → Firestore
    projects/{projectId}/phaseData.phaseHandoffs.collector
    
  4. Verify confirmed.hasDocuments = true
  5. Refresh page
  6. Send another message
  7. Verify handoff updates with latest state

Console Output:

Watch for this log on each collector message:

[AI Chat] Collector handoff persisted: {
  hasDocuments: true,
  githubConnected: false,
  extensionLinked: false,
  readyForExtraction: false
}

Future Enhancements

1. Auto-Transition to Extraction

When readyForExtraction = true, automatically switch mode:

if (handoff.readyForNextPhase) {
  await adminDb.collection('projects').doc(projectId).update({
    currentPhase: 'analyzed',
  });
  
  // Next message will be in extraction_review_mode
}

2. Visual Checklist UI

Display the checklist state in the UI:

<ChecklistCard>
  <ChecklistItem 
    checked={handoff.confirmed.hasDocuments}
    label="Documents uploaded"
  />
  <ChecklistItem 
    checked={handoff.confirmed.githubConnected}
    label="GitHub connected"
  />
  <ChecklistItem 
    checked={handoff.confirmed.extensionLinked}
    label="Extension linked"
  />
</ChecklistCard>

3. Analytics Dashboard

Track average time to complete collector phase:

  • Time from first message to readyForExtraction = true
  • Most common blockers (missing docs? no GitHub?)
  • Drop-off points

4. Smart Reminders

If user hasn't interacted in 24 hours and checklist incomplete:

  • Send email: "Hey! You're 2/3 done setting up your project..."
  • Show prompt on next login

Files Changed

  1. app/api/ai/chat/route.ts - Added handoff persistence
  2. lib/ai/prompts/collector.ts - Added structured output instructions
  3. lib/types/phase-handoff.ts - Type already existed (no changes needed)

Status

Complete and deployed

  • Collector AI returns structured handoff data
  • Handoff data persists to Firestore on every message
  • Console logging for debugging
  • No linting errors
  • Dev server restarted with changes

Summary

The Collector AI now maintains persistent checklist state in Firestore, ensuring users never lose progress and enabling future features like:

  • Auto-transitions between phases
  • Visual checklist UI
  • Analytics and reminders

Status: 🚀 Ready for testing!