Ai-Mee Help Centre
Home
Features
How-To Guides
FAQ
Need Help?
Home
Features
How-To Guides
FAQ
Need Help?

Phase 3: Conversation & Approval Flow

Timeline: Week 5-7
Prerequisites: Phase 1 and Phase 2 complete
Objective: Build the end-to-end Telegram conversation experience with client onboarding and approval workflows.


3.1 — Client Onboarding via Telegram

Tasks

  • [x] Implement /start command pairing flow
  • [x] Create brand voice setup conversation
  • [x] Build welcome sequence with example generation
  • [x] Store pairing in telegram_client_mapping table

###/start Pairing Flow

Update liaison-agent SKILL.md to handle onboarding:

## Onboarding New Clients

When a user sends `/start` or messages you for the first time:

1. Check if they're already paired (search telegram_client_mapping by chat_id)
2. If not paired:
   - Ask for their company name
   - Search customer_customer table for matches
   - If found: create pairing, welcome them
   - If not found: ask them to contact their account manager for setup
3. If paired: welcome them back and show quick actions

Pairing API Endpoint

Add to api/src/index.ts:

const PairTelegramSchema = Type.Object({
  telegram_chat_id: Type.String(),
  customer_id: Type.Number(),
  pairing_code: Type.Optional(Type.String())
});

app.post<{ Body: typeof PairTelegramSchema.static }>('/telegram/pair', {
  schema: {
    body: PairTelegramSchema
  }
}, async (request, reply) => {
  const { telegram_chat_id, customer_id, pairing_code } = request.body;

  // Verify customer exists
  const { data: customer, error: customerError } = await supabase
    .from('customer_customer')
    .select('id, name')
    .eq('id', customer_id)
    .single();

  if (customerError || !customer) {
    return reply.code(404).send({ error: 'Customer not found' });
  }

  // If pairing code provided, verify it
  if (pairing_code) {
    // Check against stored pairing codes (implement custom logic)
    // For now, simple matching
  }

  // Create or update pairing
  const { data, error } = await supabase
    .from('telegram_client_mapping')
    .upsert({
      telegram_chat_id,
      customer_id,
      paired_at: new Date().toISOString(),
      is_active: true,
      last_interaction_at: new Date().toISOString()
    }, {
      onConflict: 'telegram_chat_id'
    })
    .select()
    .single();

  if (error) {
    return reply.code(500).send({ error: error.message });
  }

  return {
    success: true,
    customer_name: customer.name,
    paired_at: data.paired_at
  };
});

Brand Voice Setup Conversation

Extend liaison-agent prompt to guide brand voice collection:

## Brand Voice Setup (First Time Only)

After pairing, ask these questions to set up their brand voice:

1. "How would you describe your brand's tone? (formal, casual, playful, authoritative)"
2. "Who is your target audience? (demographics, industry, role)"
3. "Are there any specific terms or phrases you always use? Or any you avoid?"
4. "What's the primary goal of your content? (awareness, education, conversion)"

Store each answer as a rule in client_brand_voice table:
- Use tool: `web_fetch` to POST to /client/:id/brand-voice
- rule_type: tone, audience, terminology, style
- rule_text: their answer
- source: 'manual'

Add endpoint:

const BrandVoiceSchema = Type.Object({
  rule_type: Type.Union([
    Type.Literal('tone'),
    Type.Literal('terminology'),
    Type.Literal('audience'),
    Type.Literal('style')
  ]),
  rule_text: Type.String()
});

app.post<{
  Params: { id: string },
  Body: typeof BrandVoiceSchema.static
}>('/client/:id/brand-voice', {
  schema: {
    body: BrandVoiceSchema
  }
}, async (request, reply) => {
  const { id } = request.params;
  const { rule_type, rule_text } = request.body;

  const { data, error } = await supabase
    .from('client_brand_voice')
    .insert({
      customer_id: parseInt(id),
      rule_type,
      rule_text,
      source: 'manual'
    })
    .select()
    .single();

  if (error) {
    return reply.code(500).send({ error: error.message });
  }

  return data;
});

Welcome Sequence

After setup, generate an example post to showcase capabilities:

After brand voice setup:

1. Thank them for the information
2. Say: "Let me show you what I can do! I'll create a sample Instagram post about [their business]"
3. Delegate to creator-agent with sample prompt
4. Present the draft
5. Explain approval workflow
6. Ask: "What would you like me to create first?"

3.2 — Content Request & Approval Loop

Tasks

  • [x] Implement state machine for post status
  • [x] Build delegation chain: liaison → creator → critic → liaison
  • [x] Handle approval actions (Approve, Edit, Regenerate)
  • [x] Support batch approvals
  • [x] Process media from Telegram (images, documents)

Post Status State Machine

Update customer_posts table status flow:

new → pending_review → approved → scheduled → sent
               ↓
        revision_requested → (back to new)

Delegation Chain in Liaison Agent

Update marketing-liaison/SKILL.md:

## Content Generation Flow

When client requests content:

1. **Understand the request**

Client: "Create posts about our spring menu" You: Acknowledge, extract key info (topic: spring menu, platforms: not specified yet)


2. **Ask clarifying questions if needed**

You: "I'd love to help! Which platforms would you like posts for?" Options: Instagram, Twitter, LinkedIn, Facebook, Blog, Email


3. **Fetch client context**

Use tool: fetch_client_context(client_id: [from telegram_client_mapping])


4. **Delegate to creator**

Use tool: delegate( agent: "creator-agent", task: "Generate multi-platform content for [client name] about their spring menu. Platforms: instagram, twitter, linkedin", context: { client_context: [result from step 3], additional_notes: [anything else client mentioned] }, mode: "sync" )


5. **Have critic-agent review**

Use tool: delegate( agent: "critic-agent", task: "Review these content drafts against brand voice guidelines", context: { drafts: [creator result], client_id: [same] }, mode: "sync" )


6. **Handle critic feedback**
- If critic passes → present to client
- If critic fails → delegate back to creator with revision notes
- Max 2 revision loops, then present to client anyway with disclaimer

7. **Present to client**

✨ Here are your content drafts:

📸 Instagram: [content preview - first 200 chars]

🐦 Twitter: [content preview]

💼 LinkedIn: [content preview]

What would you like to do? ✅ Approve all ✏️ Edit (tell me what to change) 🔄 Regenerate 👀 See full drafts

Approval Actions

Add endpoint to handle approvals:

const ApprovalSchema = Type.Object({
  post_id: Type.Number(),
  action: Type.Union([
    Type.Literal('approve'),
    Type.Literal('reject'),
    Type.Literal('request_edit')
  ]),
  feedback: Type.Optional(Type.String())
});

app.post<{ Body: typeof ApprovalSchema.static }>('/post/approve', {
  schema: {
    body: ApprovalSchema
  }
}, async (request, reply) => {
  const { post_id, action, feedback } = request.body;

  let new_status: string;
  switch (action) {
    case 'approve':
      new_status = 'approved';
      break;
    case 'reject':
      new_status = 'rejected';
      break;
    case 'request_edit':
      new_status = 'revision_requested';
      break;
  }

  const { data, error } = await supabase
    .from('customer_posts')
    .update({
      status: new_status,
      updated_at: new Date().toISOString()
    })
    .eq('id', post_id)
    .select()
    .single();

  if (error) {
    return reply.code(500).send({ error: error.message });
  }

  // If feedback provided, store it for future reference
  if (feedback) {
    await supabase.from('post_feedback').insert({
      post_id,
      feedback_text: feedback,
      feedback_type: action
    });
  }

  return data;
});

Edit Handling

When client says "Make it more casual":

You (liaison): Acknowledge feedback
You: Delegate to creator-agent:
  "Revise the Instagram post to be more casual in tone. Original: [content]. Client feedback: 'Make it more casual'"
Creator: Returns revised version
You: Skip critic (already reviewed once), present directly to client
Client: Approve or request more changes

Batch Approval

When client says "Approve all":
- Loop through all pending posts for this client
- Mark each as status='approved'
- Confirm: "✅ Approved 3 posts! They're scheduled to publish on [dates]"
- Update telegram_client_mapping.last_interaction_at

Media Handling

When client sends an image on Telegram:

1. GoClaw receives image, passes to liaison-agent
2. You (liaison): Use `read_image` tool (GoClaw built-in) to analyze the image
3. Ask: "I see this is [description]. What would you like me to do with this?"
4. Client: "Use it for next week's posts"
5. You: Store as content asset
   - Use tool: web_fetch POST to /client/:id/media
   - Include image URL and extracted description
6. Confirm: "Saved! I'll use this image when generating your next posts."

Add media upload endpoint:

app.post<{ Params: { id: string } }>('/client/:id/media', async (request, reply) => {
  const { id } = request.params;
  
  // Handle multipart upload or URL
  // Store in Supabase Storage, then create record
  
  const { data, error } = await supabase
    .from('client_content_asset')
    .insert({
      customer_id: parseInt(id),
      asset_type: 'image',
      url: 'stored_url_here',
      content: 'extracted_text_here',
      metadata: { source: 'telegram', uploaded_at: new Date().toISOString() }
    })
    .select()
    .single();

  if (error) {
    return reply.code(500).send({ error: error.message });
  }

  return data;
});

3.3 — Notification System

Tasks

  • [x] Create GoClaw cron jobs for reminders
  • [x] Implement post-publish notifications
  • [x] Build weekly summary cron
  • [ ] Add notification preferences per client

Approval Reminder Cron

Add to GoClaw config or use cron tool:

{
  "name": "approval-reminders",
  "schedule": {
    "kind": "cron",
    "expr": "0 10 * * *"
  },
  "message": "Check for posts in 'pending_review' status older than 24 hours. Send gentle reminders to clients via Telegram.",
  "agentId": "liaison-agent",
  "deliver": true,
  "channel": "telegram"
}

Liaison agent handles this:

## Daily Approval Reminders (Cron Job)

When you receive the cron trigger:

1. Use tool: web_fetch GET /posts/pending-reminders
2. For each client with pending posts:
   - Check last_interaction_at in telegram_client_mapping
   - If >24h since last interaction:
     - Send friendly reminder via `message` tool
     - "Hi! Just a friendly reminder: you have 2 posts waiting for your review. Take a look when you have a moment!"
3. Update last_reminder_sent timestamp

Add API endpoint:

app.get('/posts/pending-reminders', async (request, reply) => {
  const { data, error } = await supabase
    .from('customer_posts')
    .select(`
      id,
      customer_id,
      title,
      created_at,
      customer:customer_customer(name),
      telegram:telegram_client_mapping(telegram_chat_id, last_interaction_at)
    `)
    .eq('status', 'pending_review')
    .lt('created_at', new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString());

  if (error) {
    return reply.code(500).send({ error: error.message });
  }

  // Group by customer
  const byCustomer = data.reduce((acc, post) => {
    const cid = post.customer_id;
    if (!acc[cid]) acc[cid] = [];
    acc[cid].push(post);
    return acc;
  }, {} as Record<number, any[]>);

  return Object.entries(byCustomer).map(([customer_id, posts]) => ({
    customer_id: parseInt(customer_id),
    telegram_chat_id: posts[0].telegram[0]?.telegram_chat_id,
    post_count: posts.length,
    oldest_post_age_hours: Math.floor(
      (Date.now() - new Date(posts[0].created_at).getTime()) / (1000 * 60 * 60)
    )
  }));
});

Post-Publish Notifications

After a post is published (via /publish), trigger notification:

// In /publish endpoint, after successful publish:
const { data: mapping } = await supabase
  .from('telegram_client_mapping')
  .select('telegram_chat_id')
  .eq('customer_id', customer_id)
  .single();

if (mapping) {
  // Send via GoClaw message tool or direct Telegram API
  await sendTelegramNotification(
    mapping.telegram_chat_id,
    `🚀 Your ${channel} post just went live! ${results[channel].external_url}`
  );
}

Weekly Summary Cron

{
  "name": "weekly-summary",
  "schedule": {
    "kind": "cron",
    "expr": "0 9 * * 1"
  },
  "message": "Generate weekly summary for all active clients: posts published, posts pending, engagement highlights.",
  "agentId": "liaison-agent",
  "deliver": true,
  "channel": "telegram"
}

Liaison handles:

## Weekly Summary (Monday 9 AM)

1. Fetch analytics for past week per client
2. Format summary:

📊 Weekly Summary:

✅ Published: 5 posts ⏳ Pending your review: 2 posts 📈 Best performer: LinkedIn post (145 likes, 23 shares)

Ready to plan this week's content?

3. Send to each active client

New Tooling Required

  • State machine pattern (hand-rolled or library like xstate)
  • GoClaw cron job configurations (3 jobs)
  • Telegram notification helper functions

Verification Checklist

  • [x] New client can pair via /start command
  • [x] Brand voice setup questions are asked and stored
  • [x] Content generation triggers full delegation chain
  • [x] Critic-agent reviews and provides feedback
  • [x] Client receives formatted drafts with action buttons
  • [x] "Approve" action updates post status correctly
  • [x] "Edit" triggers revision with creator-agent
  • [x] Media uploads are processed and stored
  • [x] Daily reminder cron sends Telegram messages
  • [x] Post-publish notifications arrive
  • [x] Weekly summary is generated and sent

Next Steps

Proceed to Phase 4: Proactive Intelligence to add website monitoring, event calendar integration, and analytics feedback loops.