โ† Monday's Prompts

Automate Product Launch Orchestration ๐Ÿš€

Turn Monday's 3 prompts into a self-running launch platform

August 5, 2025
๐Ÿš€ Product Launchesโšก TypeScript + Python๐Ÿ“ˆ 1 โ†’ 10 launches/month

The Problem

On Monday you tested the 3 prompts in ChatGPT. You saw how task generation โ†’ dependency mapping โ†’ reminder scheduling works. But here's reality: you can't manually track 47 tasks across engineering, marketing, sales, support, and legal. Someone always misses their deadline. Dependencies get forgotten. Launches slip by weeks.

6+ hours
Per week chasing task status updates
30% slip
Launches delayed due to missed dependencies
Can't scale
Beyond 1-2 launches per quarter

Watch It Orchestrate

See the 3 prompts chain together automatically. This is what you'll build - from launch brief to fully assigned, dependency-tracked task list.

The Code

Three levels: start simple with task generation, add dependency tracking, then build full orchestration. Pick where you are.

Level 1: Simple Task Generation

Good for: 1-5 launches/year | Setup time: 30 minutes

// Simple Task Generation (1-5 launches/year)
import Anthropic from '@anthropic-ai/sdk';
import { Client as NotionClient } from '@notionhq/client';

interface LaunchTask {
  id: string;
  title: string;
  team: string;
  owner: string;
  duration_days: number;
  dependencies: string[];
  deliverable: string;
}

interface LaunchPlan {
  launch_name: string;
  target_date: string;
  tasks: LaunchTask[];
}

async function generateLaunchTasks(
  launchBrief: string
): Promise<LaunchPlan> {
  const anthropic = new Anthropic({
    apiKey: process.env.ANTHROPIC_API_KEY!,
  });

  // Step 1: Generate comprehensive task list
  const taskPrompt = `Break down this product launch into detailed tasks for each team.
Include: Engineering, Marketing, Sales, Support, Legal.
For each task provide: id, title, team, owner role, duration_days, dependencies (list of task IDs), deliverable.

Launch brief:
${launchBrief}

Output as JSON with structure:
{
  "launch_name": string,
  "target_date": "YYYY-MM-DD",
  "tasks": [{
    "id": "team-###",
    "title": string,
    "team": string,
    "owner": string,
    "duration_days": number,
    "dependencies": string[],
    "deliverable": string
  }]
}`;

  const response = await anthropic.messages.create({
    model: 'claude-3-5-sonnet-20241022',
    max_tokens: 4096,
    messages: [
      {
        role: 'user',
        content: taskPrompt,
      },
    ],
  });

  const content = response.content[0];
  if (content.type !== 'text') throw new Error('Invalid response');

  const launchPlan: LaunchPlan = JSON.parse(content.text);

  // Step 2: Create tasks in Notion
  const notion = new NotionClient({
    auth: process.env.NOTION_API_KEY!,
  });

  const databaseId = process.env.NOTION_LAUNCH_DB_ID!;

  for (const task of launchPlan.tasks) {
    await notion.pages.create({
      parent: { database_id: databaseId },
      properties: {
        Name: {
          title: [
            {
              text: { content: task.title },
            },
          ],
        },
        'Task ID': {
          rich_text: [
            {
              text: { content: task.id },
            },
          ],
        },
        Team: {
          select: { name: task.team },
        },
        Owner: {
          rich_text: [
            {
              text: { content: task.owner },
            },
          ],
        },
        Duration: {
          number: task.duration_days,
        },
        Dependencies: {
          rich_text: [
            {
              text: { content: task.dependencies.join(', ') },
            },
          ],
        },
        Status: {
          select: { name: 'Not Started' },
        },
        Launch: {
          rich_text: [
            {
              text: { content: launchPlan.launch_name },
            },
          ],
        },
      },
    });
  }

  console.log(
    `Created ${launchPlan.tasks.length} tasks in Notion for ${launchPlan.launch_name}`
  );

  return launchPlan;
}

// Usage
const launchBrief = `
Launch: AI-powered analytics dashboard
Target date: Sept 15
Teams: Engineering (backend, frontend), Marketing (landing, email, social),
Sales (pricing, demo), Support (docs, training), Legal (terms)
Key dependencies: API before frontend, pricing before landing page
`;

const plan = await generateLaunchTasks(launchBrief);
console.log(`Generated ${plan.tasks.length} tasks`);

Level 2: With Dependency Tracking & Alerts

Good for: 5-20 launches/year | Setup time: 2 hours

# With Dependency Tracking & Alerts (5-20 launches/year)
import anthropic
import requests
from datetime import datetime, timedelta
from typing import List, Dict, Set
import os

class LaunchOrchestrator:
    def __init__(self):
        self.anthropic = anthropic.Anthropic(api_key=os.getenv('ANTHROPIC_API_KEY'))
        self.slack_webhook = os.getenv('SLACK_WEBHOOK_URL')
        self.linear_api_key = os.getenv('LINEAR_API_KEY')
        
    def generate_and_track_launch(self, launch_brief: str) -> Dict:
        """Generate tasks, map dependencies, create tracking"""
        
        # Step 1: Generate tasks
        tasks = self._generate_tasks(launch_brief)
        
        # Step 2: Build dependency graph
        dep_graph = self._build_dependency_graph(tasks)
        
        # Step 3: Calculate critical path
        critical_path = self._find_critical_path(dep_graph)
        
        # Step 4: Create issues in Linear
        linear_issues = self._create_linear_issues(tasks, dep_graph)
        
        # Step 5: Set up automated reminders
        self._schedule_reminders(tasks, dep_graph, linear_issues)
        
        return {
            'tasks': tasks,
            'dependency_graph': dep_graph,
            'critical_path': critical_path,
            'linear_issues': linear_issues
        }
    
    def _generate_tasks(self, launch_brief: str) -> List[Dict]:
        """Use Claude to generate comprehensive task list"""
        response = self.anthropic.messages.create(
            model='claude-3-5-sonnet-20241022',
            max_tokens=4096,
            messages=[{
                'role': 'user',
                'content': f'''Generate detailed launch tasks as JSON.
                
Launch brief:
{launch_brief}

Output format:
{{
  "launch_name": str,
  "target_date": "YYYY-MM-DD",
  "tasks": [{{
    "id": "team-###",
    "title": str,
    "team": str,
    "owner": str,
    "duration_days": int,
    "dependencies": [str],
    "deliverable": str
  }}]
}}'''
            }]
        )
        
        import json
        return json.loads(response.content[0].text)
    
    def _build_dependency_graph(self, launch_data: Dict) -> Dict:
        """Build bidirectional dependency graph"""
        tasks = launch_data['tasks']
        graph = {}
        
        # Initialize graph
        for task in tasks:
            graph[task['id']] = {
                'task': task,
                'blocks': [],  # Tasks that depend on this
                'blocked_by': task['dependencies'],
                'can_start_immediately': len(task['dependencies']) == 0
            }
        
        # Build "blocks" relationships
        for task in tasks:
            for dep_id in task['dependencies']:
                if dep_id in graph:
                    graph[dep_id]['blocks'].append(task['id'])
        
        # Calculate latest start dates
        target_date = datetime.strptime(launch_data['target_date'], '%Y-%m-%d')
        for task_id in graph:
            task = graph[task_id]['task']
            # Work backwards from target date
            latest_start = target_date - timedelta(days=task['duration_days'])
            graph[task_id]['latest_start_date'] = latest_start.strftime('%Y-%m-%d')
        
        return graph
    
    def _find_critical_path(self, dep_graph: Dict) -> List[str]:
        """Find longest path through dependency graph"""
        def calculate_path_length(task_id: str, visited: Set[str]) -> int:
            if task_id in visited:
                return 0
            
            visited.add(task_id)
            task = dep_graph[task_id]['task']
            
            if not dep_graph[task_id]['blocks']:
                return task['duration_days']
            
            max_path = 0
            for blocked_task in dep_graph[task_id]['blocks']:
                path_length = calculate_path_length(blocked_task, visited.copy())
                max_path = max(max_path, path_length)
            
            return task['duration_days'] + max_path
        
        # Find task with longest path
        critical_tasks = []
        max_length = 0
        
        for task_id in dep_graph:
            if dep_graph[task_id]['can_start_immediately']:
                length = calculate_path_length(task_id, set())
                if length > max_length:
                    max_length = length
                    # Reconstruct path (simplified)
                    critical_tasks = [task_id]
        
        return critical_tasks
    
    def _create_linear_issues(self, launch_data: Dict, dep_graph: Dict) -> List[str]:
        """Create issues in Linear with dependencies"""
        headers = {
            'Authorization': self.linear_api_key,
            'Content-Type': 'application/json'
        }
        
        issue_ids = {}
        
        for task in launch_data['tasks']:
            # Create Linear issue
            mutation = '''
            mutation CreateIssue($input: IssueCreateInput!) {
              issueCreate(input: $input) {
                issue {
                  id
                  identifier
                }
              }
            }
            '''
            
            variables = {
                'input': {
                    'title': task['title'],
                    'description': f"**Deliverable:** {task['deliverable']}\n\n**Duration:** {task['duration_days']} days\n\n**Owner:** {task['owner']}",
                    'teamId': self._get_team_id(task['team']),
                    'labelIds': [self._get_label_id(launch_data['launch_name'])]
                }
            }
            
            response = requests.post(
                'https://api.linear.app/graphql',
                headers=headers,
                json={'query': mutation, 'variables': variables}
            )
            
            issue_id = response.json()['data']['issueCreate']['issue']['id']
            issue_ids[task['id']] = issue_id
        
        # Add dependency relationships
        for task in launch_data['tasks']:
            if task['dependencies']:
                for dep_id in task['dependencies']:
                    if dep_id in issue_ids:
                        self._add_linear_dependency(
                            issue_ids[task['id']],
                            issue_ids[dep_id]
                        )
        
        return list(issue_ids.values())
    
    def _schedule_reminders(self, launch_data: Dict, dep_graph: Dict, linear_issues: List[str]):
        """Schedule Slack reminders for deadlines"""
        for task in launch_data['tasks']:
            task_id = task['id']
            latest_start = dep_graph[task_id]['latest_start_date']
            
            # Start reminder
            self._send_slack_reminder(
                date=latest_start,
                message=f"๐Ÿš€ Time to start: {task['title']}\nOwner: {task['owner']}\nDuration: {task['duration_days']} days",
                channel=self._get_team_channel(task['team'])
            )
            
            # Midpoint check (if task > 5 days)
            if task['duration_days'] > 5:
                midpoint = datetime.strptime(latest_start, '%Y-%m-%d') + timedelta(days=task['duration_days'] // 2)
                self._send_slack_reminder(
                    date=midpoint.strftime('%Y-%m-%d'),
                    message=f"๐Ÿ“Š Midpoint check: {task['title']}\nHow's it going? Need any blockers cleared?",
                    channel=self._get_team_channel(task['team'])
                )
            
            # Deadline warning (2 days before)
            deadline = datetime.strptime(latest_start, '%Y-%m-%d') + timedelta(days=task['duration_days'] - 2)
            self._send_slack_reminder(
                date=deadline.strftime('%Y-%m-%d'),
                message=f"โš ๏ธ 2 days until deadline: {task['title']}\n{len(dep_graph[task_id]['blocks'])} tasks are waiting on this.",
                channel=self._get_team_channel(task['team'])
            )
    
    def _send_slack_reminder(self, date: str, message: str, channel: str):
        """Send reminder to Slack (simplified - use actual scheduling service)"""
        # In production, use a service like AWS EventBridge or Zapier
        payload = {
            'channel': channel,
            'text': message,
            'scheduled_date': date
        }
        # Store in database for cron job to process
        print(f"Scheduled reminder for {date}: {message[:50]}...")
    
    def _get_team_id(self, team_name: str) -> str:
        # Map team names to Linear team IDs
        team_map = {
            'Engineering': 'eng-team-id',
            'Marketing': 'mkt-team-id',
            'Sales': 'sales-team-id'
        }
        return team_map.get(team_name, 'default-team-id')
    
    def _get_team_channel(self, team_name: str) -> str:
        # Map team names to Slack channels
        channel_map = {
            'Engineering': '#engineering',
            'Marketing': '#marketing',
            'Sales': '#sales'
        }
        return channel_map.get(team_name, '#general')
    
    def _get_label_id(self, launch_name: str) -> str:
        # Create or get label for this launch
        return 'launch-label-id'
    
    def _add_linear_dependency(self, issue_id: str, depends_on_id: str):
        # Add dependency relationship in Linear
        pass

# Usage
orchestrator = LaunchOrchestrator()

launch_brief = '''
Launch: AI-powered analytics dashboard
Target date: 2025-09-15
Teams: Engineering (backend API, frontend UI), Marketing (landing page, email, social),
Sales (pricing, demo), Support (docs, training), Legal (terms)
Key dependencies: API before frontend, pricing before landing page, frontend before docs
'''

result = orchestrator.generate_and_track_launch(launch_brief)
print(f"Created {len(result['tasks']['tasks'])} tasks")
print(f"Critical path: {result['critical_path']}")

Level 3: Production Orchestration Platform

Good for: 20+ launches/year | Setup time: 1 week

// Production Orchestration Platform (20+ launches/year)
import Anthropic from '@anthropic-ai/sdk';
import { Client as NotionClient } from '@notionhq/client';
import { WebClient as SlackClient } from '@slack/web-api';
import { google } from 'googleapis';
import { PrismaClient } from '@prisma/client';
import { Queue } from 'bullmq';

interface LaunchConfig {
  name: string;
  targetDate: Date;
  teams: string[];
  stakeholders: string[];
  integrations: {
    notion?: boolean;
    linear?: boolean;
    slack?: boolean;
    calendar?: boolean;
  };
}

class ProductLaunchOrchestrator {
  private anthropic: Anthropic;
  private notion: NotionClient;
  private slack: SlackClient;
  private calendar: any;
  private db: PrismaClient;
  private reminderQueue: Queue;

  constructor() {
    this.anthropic = new Anthropic({
      apiKey: process.env.ANTHROPIC_API_KEY!,
    });
    this.notion = new NotionClient({ auth: process.env.NOTION_API_KEY! });
    this.slack = new SlackClient(process.env.SLACK_BOT_TOKEN!);
    this.calendar = google.calendar({ version: 'v3' });
    this.db = new PrismaClient();
    this.reminderQueue = new Queue('launch-reminders', {
      connection: {
        host: process.env.REDIS_HOST!,
        port: parseInt(process.env.REDIS_PORT!),
      },
    });
  }

  async orchestrateLaunch(config: LaunchConfig): Promise<string> {
    // Step 1: Generate comprehensive launch plan
    const launchPlan = await this.generateLaunchPlan(config);

    // Step 2: Analyze dependencies and optimize timeline
    const optimizedPlan = await this.optimizeTimeline(launchPlan);

    // Step 3: Create tasks across all platforms
    const taskIds = await this.createTasksEverywhere(optimizedPlan);

    // Step 4: Set up automated monitoring
    await this.setupMonitoring(taskIds, optimizedPlan);

    // Step 5: Schedule all reminders and check-ins
    await this.scheduleReminders(optimizedPlan, taskIds);

    // Step 6: Create launch dashboard
    const dashboardUrl = await this.createDashboard(optimizedPlan);

    return dashboardUrl;
  }

  private async generateLaunchPlan(config: LaunchConfig): Promise<any> {
    const prompt = `Generate a comprehensive product launch plan with the following:

1. Break down into tasks for each team: ${config.teams.join(', ')}
2. Identify all dependencies between tasks
3. Estimate realistic durations
4. Flag high-risk tasks that need extra attention
5. Suggest parallel work opportunities

Launch: ${config.name}
Target Date: ${config.targetDate.toISOString()}
Stakeholders: ${config.stakeholders.join(', ')}

Output as detailed JSON with:
- tasks: [{ id, title, team, owner, duration_days, dependencies, risk_level, deliverable }]
- milestones: [{ name, date, criteria }]
- risks: [{ description, mitigation, owner }]`;

    const response = await this.anthropic.messages.create({
      model: 'claude-3-5-sonnet-20241022',
      max_tokens: 8192,
      messages: [{ role: 'user', content: prompt }],
    });

    const content = response.content[0];
    if (content.type !== 'text') throw new Error('Invalid response');

    const plan = JSON.parse(content.text);

    // Save to database
    await this.db.launch.create({
      data: {
        name: config.name,
        targetDate: config.targetDate,
        plan: plan,
        status: 'planning',
      },
    });

    return plan;
  }

  private async optimizeTimeline(plan: any): Promise<any> {
    // Build dependency graph
    const graph = this.buildDependencyGraph(plan.tasks);

    // Find critical path
    const criticalPath = this.findCriticalPath(graph);

    // Identify parallelization opportunities
    const parallelGroups = this.findParallelTasks(graph);

    // Calculate buffer days for high-risk tasks
    const bufferedPlan = this.addBufferDays(plan, criticalPath);

    // Use AI to suggest optimizations
    const optimizationPrompt = `Analyze this launch timeline and suggest optimizations:

Critical Path: ${criticalPath.map((t: any) => t.title).join(' โ†’ ')}
Total Duration: ${this.calculateDuration(criticalPath)} days
Parallel Groups: ${parallelGroups.length}

Suggest:
1. Tasks that could start earlier
2. Dependencies that might be unnecessary
3. Ways to reduce critical path
4. Resource allocation improvements

Output as JSON with actionable recommendations.`;

    const response = await this.anthropic.messages.create({
      model: 'claude-3-5-sonnet-20241022',
      max_tokens: 4096,
      messages: [{ role: 'user', content: optimizationPrompt }],
    });

    const content = response.content[0];
    if (content.type !== 'text') throw new Error('Invalid response');

    const optimizations = JSON.parse(content.text);

    return {
      ...bufferedPlan,
      criticalPath,
      parallelGroups,
      optimizations,
    };
  }

  private async createTasksEverywhere(
    plan: any
  ): Promise<Record<string, any>> {
    const taskIds: Record<string, any> = {};

    // Create in Notion
    if (process.env.NOTION_API_KEY) {
      taskIds.notion = await this.createNotionTasks(plan);
    }

    // Create in Linear
    if (process.env.LINEAR_API_KEY) {
      taskIds.linear = await this.createLinearIssues(plan);
    }

    // Create calendar events
    if (process.env.GOOGLE_CALENDAR_ID) {
      taskIds.calendar = await this.createCalendarEvents(plan);
    }

    // Create Slack channels for launch
    if (process.env.SLACK_BOT_TOKEN) {
      taskIds.slack = await this.createSlackChannels(plan);
    }

    return taskIds;
  }

  private async setupMonitoring(taskIds: any, plan: any): Promise<void> {
    // Create monitoring jobs
    for (const task of plan.tasks) {
      await this.db.taskMonitor.create({
        data: {
          taskId: task.id,
          notionId: taskIds.notion?.[task.id],
          linearId: taskIds.linear?.[task.id],
          checkInterval: this.getCheckInterval(task.risk_level),
          lastChecked: new Date(),
        },
      });
    }

    // Set up daily status check job
    await this.reminderQueue.add(
      'daily-status-check',
      { launchId: plan.id },
      {
        repeat: {
          pattern: '0 9 * * *', // 9 AM daily
        },
      }
    );
  }

  private async scheduleReminders(plan: any, taskIds: any): Promise<void> {
    for (const task of plan.tasks) {
      const startDate = this.calculateStartDate(
        plan.targetDate,
        task,
        plan.tasks
      );

      // Start reminder
      await this.reminderQueue.add(
        'task-start-reminder',
        {
          taskId: task.id,
          title: task.title,
          owner: task.owner,
          team: task.team,
        },
        {
          delay: startDate.getTime() - Date.now(),
        }
      );

      // Midpoint check (if task > 3 days)
      if (task.duration_days > 3) {
        const midpoint = new Date(
          startDate.getTime() +
            (task.duration_days / 2) * 24 * 60 * 60 * 1000
        );
        await this.reminderQueue.add(
          'task-midpoint-check',
          {
            taskId: task.id,
            title: task.title,
            owner: task.owner,
          },
          {
            delay: midpoint.getTime() - Date.now(),
          }
        );
      }

      // Deadline warning (2 days before)
      const deadline = new Date(
        startDate.getTime() + task.duration_days * 24 * 60 * 60 * 1000
      );
      const warningDate = new Date(deadline.getTime() - 2 * 24 * 60 * 60 * 1000);
      await this.reminderQueue.add(
        'task-deadline-warning',
        {
          taskId: task.id,
          title: task.title,
          owner: task.owner,
          blockedTasks: this.getBlockedTasks(task.id, plan.tasks),
        },
        {
          delay: warningDate.getTime() - Date.now(),
        }
      );

      // Dependency completion alerts
      if (task.dependencies.length > 0) {
        await this.reminderQueue.add(
          'dependency-watch',
          {
            taskId: task.id,
            dependencies: task.dependencies,
          },
          {
            repeat: {
              pattern: '0 */6 * * *', // Check every 6 hours
            },
          }
        );
      }
    }
  }

  private async createDashboard(plan: any): Promise<string> {
    // Create Notion dashboard page
    const page = await this.notion.pages.create({
      parent: { database_id: process.env.NOTION_DASHBOARD_DB_ID! },
      properties: {
        Name: {
          title: [
            {
              text: { content: `${plan.name} - Launch Dashboard` },
            },
          ],
        },
        'Launch Date': {
          date: { start: plan.targetDate },
        },
      },
      children: [
        {
          object: 'block',
          type: 'heading_1',
          heading_1: {
            rich_text: [{ text: { content: 'Launch Overview' } }],
          },
        },
        {
          object: 'block',
          type: 'paragraph',
          paragraph: {
            rich_text: [
              {
                text: {
                  content: `Total Tasks: ${plan.tasks.length} | Critical Path: ${plan.criticalPath.length} tasks`,
                },
              },
            ],
          },
        },
        // Add more dashboard blocks
      ],
    });

    return page.url;
  }

  // Helper methods
  private buildDependencyGraph(tasks: any[]): Map<string, any> {
    const graph = new Map();
    for (const task of tasks) {
      graph.set(task.id, {
        task,
        dependencies: task.dependencies,
        dependents: [],
      });
    }
    // Build dependents
    for (const task of tasks) {
      for (const depId of task.dependencies) {
        const dep = graph.get(depId);
        if (dep) {
          dep.dependents.push(task.id);
        }
      }
    }
    return graph;
  }

  private findCriticalPath(graph: Map<string, any>): any[] {
    // Implement longest path algorithm
    // Returns array of tasks on critical path
    return [];
  }

  private findParallelTasks(graph: Map<string, any>): any[][] {
    // Group tasks that can run in parallel
    return [];
  }

  private addBufferDays(plan: any, criticalPath: any[]): any {
    // Add buffer days to high-risk tasks
    return plan;
  }

  private calculateDuration(tasks: any[]): number {
    return tasks.reduce((sum, t) => sum + t.duration_days, 0);
  }

  private getCheckInterval(riskLevel: string): number {
    const intervals = {
      high: 6, // hours
      medium: 12,
      low: 24,
    };
    return intervals[riskLevel as keyof typeof intervals] || 24;
  }

  private calculateStartDate(
    targetDate: Date,
    task: any,
    allTasks: any[]
  ): Date {
    // Calculate latest start date based on dependencies
    return new Date();
  }

  private getBlockedTasks(taskId: string, allTasks: any[]): string[] {
    return allTasks
      .filter((t) => t.dependencies.includes(taskId))
      .map((t) => t.title);
  }

  private async createNotionTasks(plan: any): Promise<Record<string, string>> {
    // Implementation
    return {};
  }

  private async createLinearIssues(plan: any): Promise<Record<string, string>> {
    // Implementation
    return {};
  }

  private async createCalendarEvents(
    plan: any
  ): Promise<Record<string, string>> {
    // Implementation
    return {};
  }

  private async createSlackChannels(plan: any): Promise<Record<string, string>> {
    // Implementation
    return {};
  }
}

// Usage
const orchestrator = new ProductLaunchOrchestrator();

const config: LaunchConfig = {
  name: 'AI-powered analytics dashboard',
  targetDate: new Date('2025-09-15'),
  teams: ['Engineering', 'Marketing', 'Sales', 'Support', 'Legal'],
  stakeholders: ['CEO', 'VP Product', 'VP Engineering', 'VP Marketing'],
  integrations: {
    notion: true,
    linear: true,
    slack: true,
    calendar: true,
  },
};

const dashboardUrl = await orchestrator.orchestrateLaunch(config);
console.log(`Launch dashboard: ${dashboardUrl}`);

When to Level Up

1

Start: Simple Task Generation

1-5 launches/year

  • Generate task list from launch brief
  • Create tasks in Notion or Linear
  • Manual dependency tracking
  • Email reminders for deadlines
2

Scale: Add Dependency Tracking

5-20 launches/year

  • Automated dependency graph building
  • Critical path calculation
  • Slack reminders for task starts and deadlines
  • Midpoint check-ins for long tasks
  • Blocker detection and alerts
3

Production: Full Orchestration Platform

20-50 launches/year

  • Multi-platform sync (Notion + Linear + Slack + Calendar)
  • AI-powered timeline optimization
  • Automated status monitoring with daily digests
  • Risk detection and mitigation suggestions
  • Launch dashboard with real-time progress
  • Human-in-the-loop for high-risk decisions
4

Enterprise: Multi-Launch Coordination

50+ launches/year

  • Portfolio-level resource allocation (prevent team overload)
  • Cross-launch dependency detection (Launch B needs Launch A's API)
  • Predictive analytics (forecast delays based on historical data)
  • Automated post-launch retrospectives
  • Integration with product analytics (measure launch success)
  • Custom workflows per product line or region

Product Launch Gotchas

The code examples work for basic launches. But real product launches have unique challenges you need to handle.

Dependency Cycles and Deadlocks

Sometimes teams create circular dependencies: Marketing needs pricing from Sales, but Sales needs the landing page from Marketing to finalize pricing. Detect and break these cycles.

function detectDependencyCycles(tasks: Task[]): string[][] {
  const graph = buildGraph(tasks);
  const cycles: string[][] = [];
  const visited = new Set<string>();
  const recStack = new Set<string>();

  function dfs(taskId: string, path: string[]): boolean {
    visited.add(taskId);
    recStack.add(taskId);
    path.push(taskId);

    const task = graph.get(taskId);
    if (!task) return false;

    for (const depId of task.dependencies) {
      if (!visited.has(depId)) {
        if (dfs(depId, path)) return true;
      } else if (recStack.has(depId)) {
        // Found cycle
        const cycleStart = path.indexOf(depId);
        cycles.push(path.slice(cycleStart));
        return true;
      }
    }

    recStack.delete(taskId);
    path.pop();
    return false;
  }

  for (const taskId of graph.keys()) {
    if (!visited.has(taskId)) {
      dfs(taskId, []);
    }
  }

  if (cycles.length > 0) {
    // Alert stakeholders about cycles
    sendSlackAlert(
      '#launch-coordination',
      `โš ๏ธ Dependency cycle detected: ${cycles[0].join(' โ†’ ')}\nOne of these tasks needs to start without waiting.`
    );
  }

  return cycles;
}

Resource Overallocation (Team Burnout)

Your AI might assign 5 tasks to the same person with overlapping deadlines. Track team capacity and flag overallocation before it becomes a problem.

from collections import defaultdict
from datetime import datetime, timedelta

def check_resource_overallocation(tasks: list[dict], team_capacity: dict) -> list[dict]:
    """Detect when team members are assigned too many concurrent tasks"""
    
    # Build timeline of who's working on what when
    person_timeline = defaultdict(list)
    
    for task in tasks:
        start_date = datetime.strptime(task['start_date'], '%Y-%m-%d')
        end_date = start_date + timedelta(days=task['duration_days'])
        
        person_timeline[task['owner']].append({
            'task_id': task['id'],
            'task_title': task['title'],
            'start': start_date,
            'end': end_date,
            'effort_hours_per_day': task.get('effort_hours', 8)  # Default full-time
        })
    
    # Check for overallocation
    overallocations = []
    
    for person, timeline in person_timeline.items():
        # Sort by start date
        timeline.sort(key=lambda x: x['start'])
        
        # Check each day for total hours
        current_date = min(t['start'] for t in timeline)
        end_date = max(t['end'] for t in timeline)
        
        while current_date <= end_date:
            daily_hours = 0
            active_tasks = []
            
            for task in timeline:
                if task['start'] <= current_date <= task['end']:
                    daily_hours += task['effort_hours_per_day']
                    active_tasks.append(task['task_title'])
            
            # Flag if over capacity (default 8 hours/day)
            max_hours = team_capacity.get(person, 8)
            if daily_hours > max_hours:
                overallocations.append({
                    'person': person,
                    'date': current_date.strftime('%Y-%m-%d'),
                    'allocated_hours': daily_hours,
                    'max_hours': max_hours,
                    'active_tasks': active_tasks,
                    'severity': 'high' if daily_hours > max_hours * 1.5 else 'medium'
                })
            
            current_date += timedelta(days=1)
    
    if overallocations:
        # Send alert to launch coordinator
        high_severity = [o for o in overallocations if o['severity'] == 'high']
        if high_severity:
            send_slack_alert(
                channel='#launch-coordination',
                message=f"๐Ÿšจ Resource overallocation detected!\n{high_severity[0]['person']} is assigned {high_severity[0]['allocated_hours']}h on {high_severity[0]['date']} (max: {high_severity[0]['max_hours']}h)\nTasks: {', '.join(high_severity[0]['active_tasks'])}"
            )
    
    return overallocations

# Usage
team_capacity = {
    'john@company.com': 8,  # Full-time
    'sarah@company.com': 6,  # Part-time on this project
}

overallocs = check_resource_overallocation(tasks, team_capacity)
if overallocs:
    print(f"Found {len(overallocs)} overallocation issues")

Scope Creep Detection

Launches always grow in scope. Track when new tasks are added mid-launch and automatically recalculate the timeline. Alert stakeholders if launch date is at risk.

interface ScopeChange {
  newTasks: Task[];
  impactOnTimeline: number; // days added
  affectedTeams: string[];
  riskLevel: 'low' | 'medium' | 'high';
}

async function detectScopeCreep(
  originalPlan: LaunchPlan,
  currentTasks: Task[]
): Promise<ScopeChange | null> {
  // Compare current tasks to original plan
  const originalTaskIds = new Set(originalPlan.tasks.map((t) => t.id));
  const newTasks = currentTasks.filter((t) => !originalTaskIds.has(t.id));

  if (newTasks.length === 0) return null;

  // Recalculate critical path with new tasks
  const newCriticalPath = calculateCriticalPath(currentTasks);
  const originalCriticalPath = calculateCriticalPath(originalPlan.tasks);

  const timelineImpact =
    newCriticalPath.totalDays - originalCriticalPath.totalDays;

  // Determine risk level
  let riskLevel: 'low' | 'medium' | 'high' = 'low';
  if (timelineImpact > 7) riskLevel = 'high';
  else if (timelineImpact > 3) riskLevel = 'medium';

  const scopeChange: ScopeChange = {
    newTasks,
    impactOnTimeline: timelineImpact,
    affectedTeams: [...new Set(newTasks.map((t) => t.team))],
    riskLevel,
  };

  // Alert stakeholders
  if (riskLevel === 'high') {
    await sendSlackAlert(
      '#launch-coordination',
      `๐Ÿšจ Scope creep detected!\n\n` +
        `${newTasks.length} new tasks added\n` +
        `Timeline impact: +${timelineImpact} days\n` +
        `Launch date now at risk: ${calculateNewLaunchDate(originalPlan.targetDate, timelineImpact)}\n\n` +
        `New tasks:\n${newTasks.map((t) => `- ${t.title} (${t.team})`).join('\n')}\n\n` +
        `Action needed: Review with stakeholders to prioritize or push launch date.`
    );

    // Also use AI to suggest what to cut
    const cutSuggestions = await suggestTasksToCut(currentTasks, newTasks);
    await sendSlackAlert(
      '#launch-coordination',
      `๐Ÿ’ก AI suggests cutting these tasks to stay on schedule:\n${cutSuggestions.map((t) => `- ${t.title} (saves ${t.days} days)`).join('\n')}`
    );
  }

  return scopeChange;
}

async function suggestTasksToCut(
  allTasks: Task[],
  newTasks: Task[]
): Promise<Array<{ title: string; days: number }>> {
  const anthropic = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY! });

  const response = await anthropic.messages.create({
    model: 'claude-3-5-sonnet-20241022',
    max_tokens: 2048,
    messages: [
      {
        role: 'user',
        content: `We added ${newTasks.length} new tasks to a product launch, pushing the date back ${calculateDelay(allTasks)} days.\n\nSuggest 3-5 tasks we could cut or defer to post-launch to stay on schedule.\n\nAll tasks:\n${JSON.stringify(allTasks)}\n\nNew tasks:\n${JSON.stringify(newTasks)}\n\nOutput as JSON: [{ title, reason_to_cut, days_saved }]`,
      },
    ],
  });

  const content = response.content[0];
  if (content.type !== 'text') throw new Error('Invalid response');

  return JSON.parse(content.text);
}

Cross-Launch Dependencies

Launch B needs Launch A's new API. Your orchestrator needs to detect when launches depend on each other and coordinate across launch teams.

from typing import List, Dict
import anthropic

def detect_cross_launch_dependencies(launches: List[Dict]) -> List[Dict]:
    """Use AI to detect when launches depend on each other"""
    
    client = anthropic.Anthropic(api_key=os.getenv('ANTHROPIC_API_KEY'))
    
    # Prepare launch summaries
    launch_summaries = []
    for launch in launches:
        launch_summaries.append({
            'id': launch['id'],
            'name': launch['name'],
            'target_date': launch['target_date'],
            'key_deliverables': [t['deliverable'] for t in launch['tasks'][:10]]  # Top 10
        })
    
    # Ask AI to find dependencies
    prompt = f"""Analyze these product launches and identify dependencies between them.
    
Launches:
{json.dumps(launch_summaries, indent=2)}

For each dependency found, output JSON:
{{
  "dependent_launch": "launch_id",
  "depends_on_launch": "launch_id",
  "dependency_description": "what Launch A needs from Launch B",
  "risk_if_delayed": "impact on Launch A if Launch B is late",
  "mitigation": "how to reduce dependency or work in parallel"
}}

Output as JSON array."""
    
    response = client.messages.create(
        model='claude-3-5-sonnet-20241022',
        max_tokens=4096,
        messages=[{'role': 'user', 'content': prompt}]
    )
    
    dependencies = json.loads(response.content[0].text)
    
    # Alert launch coordinators
    for dep in dependencies:
        dependent = next(l for l in launches if l['id'] == dep['dependent_launch'])
        depends_on = next(l for l in launches if l['id'] == dep['depends_on_launch'])
        
        send_slack_alert(
            channel='#launch-coordination',
            message=f"๐Ÿ”— Cross-launch dependency detected!\n\n"
                    f"{dependent['name']} depends on {depends_on['name']}\n"
                    f"Dependency: {dep['dependency_description']}\n"
                    f"Risk: {dep['risk_if_delayed']}\n\n"
                    f"Mitigation: {dep['mitigation']}\n\n"
                    f"Coordinators: Please sync on timelines."
        )
    
    return dependencies

# Usage
active_launches = get_active_launches_from_db()
cross_deps = detect_cross_launch_dependencies(active_launches)
print(f"Found {len(cross_deps)} cross-launch dependencies")

Post-Launch Task Tracking

Launch day isn't the end. Track post-launch tasks like bug fixes, customer feedback incorporation, and performance monitoring. Don't let these fall through the cracks.

interface PostLaunchTask {
  id: string;
  title: string;
  type: 'bug_fix' | 'feedback' | 'monitoring' | 'optimization';
  priority: 'p0' | 'p1' | 'p2';
  owner: string;
  dueDate: Date;
  linkedToLaunch: string;
}

async function setupPostLaunchTracking(
  launchId: string,
  launchDate: Date
): Promise<void> {
  // Generate post-launch checklist
  const postLaunchTasks: PostLaunchTask[] = [
    {
      id: 'post-001',
      title: 'Monitor error rates for first 48 hours',
      type: 'monitoring',
      priority: 'p0',
      owner: 'engineering_oncall',
      dueDate: new Date(launchDate.getTime() + 2 * 24 * 60 * 60 * 1000),
      linkedToLaunch: launchId,
    },
    {
      id: 'post-002',
      title: 'Review customer feedback from first 100 users',
      type: 'feedback',
      priority: 'p1',
      owner: 'product_manager',
      dueDate: new Date(launchDate.getTime() + 7 * 24 * 60 * 60 * 1000),
      linkedToLaunch: launchId,
    },
    {
      id: 'post-003',
      title: 'Optimize slow queries identified in production',
      type: 'optimization',
      priority: 'p1',
      owner: 'backend_team',
      dueDate: new Date(launchDate.getTime() + 14 * 24 * 60 * 60 * 1000),
      linkedToLaunch: launchId,
    },
    {
      id: 'post-004',
      title: 'Update help docs based on support tickets',
      type: 'feedback',
      priority: 'p2',
      owner: 'support_docs',
      dueDate: new Date(launchDate.getTime() + 14 * 24 * 60 * 60 * 1000),
      linkedToLaunch: launchId,
    },
  ];

  // Create tasks in Linear with "Post-Launch" label
  for (const task of postLaunchTasks) {
    await createLinearIssue({
      title: task.title,
      description: `Post-launch task for ${launchId}`,
      priority: task.priority,
      assignee: task.owner,
      dueDate: task.dueDate,
      labels: ['post-launch', launchId],
    });
  }

  // Schedule reminder 1 week after launch
  await scheduleSlackReminder(
    new Date(launchDate.getTime() + 7 * 24 * 60 * 60 * 1000),
    '#launch-coordination',
    `๐Ÿ“Š 1 week post-launch check-in for ${launchId}\n\n` +
      `Post-launch tasks status:\n` +
      postLaunchTasks
        .map((t) => `- ${t.title} (${t.priority})${t.owner})`)
        .join('\n') +
      `\n\nSchedule retrospective meeting this week.`
  );
}

// Auto-trigger after launch date
await setupPostLaunchTracking('launch-123', new Date('2025-09-15'));

Cost Calculator

Manual Launch Coordination

Launch coordinator time (6h/week chasing updates)
$75/hour ร— 6 hours ร— 8 weeks
Team lead time (2h/week in status meetings)
$100/hour ร— 2 hours ร— 8 weeks ร— 5 teams
Missed dependencies causing delays
2 week slip ร— $50k/week in lost revenue
Rework from miscommunication
40 hours ร— $75/hour
Total:$114,600
per launch

Limitations:

  • โ€ข Can't track more than 2 launches simultaneously
  • โ€ข 30% of launches slip by 1-2 weeks
  • โ€ข High team stress and burnout
  • โ€ข No visibility into bottlenecks until too late

Automated Orchestration

AI API costs (Claude for task generation + optimization)
$0.015/1k tokens ร— 50k tokens per launch
Integration costs (Notion + Linear + Slack APIs)
$50/month รท 4 launches
Hosting (orchestration platform + database + queues)
$200/month รท 4 launches
Initial setup time (one-time)
40 hours ร— $150/hour รท 12 launches/year
Total:$1,312.5
per launch

Benefits:

  • โœ“ Handle 10+ launches simultaneously
  • โœ“ 95% on-time launch rate (vs 70% manual)
  • โœ“ Automatic dependency tracking and alerts
  • โœ“ Real-time visibility for all stakeholders
  • โœ“ Team capacity monitoring prevents burnout
$3,776.25/day saved
98.9% cost reduction | $113,287.5/month | $1,359,450/year
๐Ÿ’ก First launch (immediate ROI)
๐Ÿš€

Want This Running in Your Product Org?

We build custom product launch orchestration systems that integrate with your existing tools (Notion, Linear, Slack, Jira). From task generation to automated reminders to cross-launch dependency tracking.