Back to Blog

Building AI Copilots: A Developer's Guide

Ultrion TeamJuly 18, 202613 min read

Building AI Copilots: A Developer's Guide

How to build AI copilots that integrate seamlessly into your product.


AI copilots are embedded assistants that help users accomplish tasks within your application. Unlike chatbots, copilots understand your product's context, can take actions, and proactively suggest next steps. This guide covers how to build them.


What Makes a Copilot Different

Feature Chatbot Copilot
Context Conversation only App state + user data
Actions Can only talk Can modify the app
Initiation User-initiated Proactive suggestions
Integration Standalone Embedded in product
Personalization None Knows user preferences

Copilot Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              Your Application            β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚          User Interface          β”‚    β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”‚    β”‚
β”‚  β”‚  β”‚     Copilot Panel         β”‚   β”‚    β”‚
β”‚  β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚   β”‚    β”‚
β”‚  β”‚  β”‚  β”‚   Chat Interface    β”‚  β”‚   β”‚    β”‚
β”‚  β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚   β”‚    β”‚
β”‚  β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚   β”‚    β”‚
β”‚  β”‚  β”‚  β”‚  Suggested Actions  β”‚  β”‚   β”‚    β”‚
β”‚  β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚   β”‚    β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β”‚    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”    β”‚
β”‚  β”‚       Copilot Backend           β”‚    β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”‚    β”‚
β”‚  β”‚  β”‚ Context  β”‚  Action  β”‚       β”‚    β”‚
β”‚  β”‚  β”‚ Provider β”‚  Router  β”‚       β”‚    β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β”‚    β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”       β”‚    β”‚
β”‚  β”‚  β”‚   LLM    β”‚   Tool   β”‚       β”‚    β”‚
β”‚  β”‚  β”‚ Engine   β”‚ Executor β”‚       β”‚    β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜       β”‚    β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜    β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Building the Context Provider

class CopilotContext {
  // Gather context from the application state
  async buildContext(userId: string, currentPage: string): Promise<Context> {
    return {
      // User context
      user: await this.getUser(userId),

      // Current page context
      page: {
        route: currentPage,
        title: this.getPageTitle(currentPage),
        elements: await this.getVisibleElements(currentPage),
      },

      // Recent actions
      recentActions: await this.getRecentActions(userId, limit=10),

      // User preferences
      preferences: await this.getUserPreferences(userId),

      // App-specific data
      data: await this.getRelevantData(userId, currentPage),
    };
  }

  async getRelevantData(userId, page) {
    // Return data relevant to what user is looking at
    switch (page) {
      case "/dashboard":
        return { metrics: await getDashboardMetrics(userId) };
      case "/projects":
        return { projects: await getProjects(userId) };
      case "/settings":
        return { settings: await getSettings(userId) };
    }
  }
}

Building the Action Router

class CopilotActionRouter {
  // Define actions the copilot can take
  actions = {
    // Navigation actions
    "navigate": async (params) => {
      window.location.href = params.url;
    },

    // Data actions
    "create_item": async (params) => {
      return await api.create(params.resource, params.data);
    },
    "update_item": async (params) => {
      return await api.update(params.resource, params.id, params.data);
    },
    "delete_item": async (params) => {
      return await api.delete(params.resource, params.id);
    },

    // UI actions
    "open_modal": async (params) => {
      ui.openModal(params.modal, params.data);
    },
    "show_notification": async (params) => {
      ui.notify(params.message, params.type);
    },
    "highlight_element": async (params) => {
      ui.highlight(params.selector, params.duration);
    },

    // Help actions
    "show_help": async (params) => {
      ui.openHelp(params.topic);
    },
    "start_tutorial": async (params) => {
      ui.startTutorial(params.tutorialId);
    },
  };

  async execute(actionName: string, params: any) {
    const action = this.actions[actionName];
    if (!action) throw new Error(`Unknown action: ${actionName}`);

    // Log for audit
    await this.logAction(actionName, params);

    // Execute
    return await action(params);
  }
}

The Copilot Agent

class Copilot {
  constructor(
    private context: CopilotContext,
    private actions: CopilotActionRouter,
  ) {}

  async process(userMessage: string, userId: string, page: string): Promise<string> {
    // 1. Build context
    const ctx = await this.context.buildContext(userId, page);

    // 2. Create system prompt with context
    const systemPrompt = this.buildPrompt(ctx);

    // 3. Get available actions for this page
    const availableActions = this.getAvailableActions(page);

    // 4. Call LLM
    const response = await this.llm.complete({
      model: "gpt-4o",
      system: systemPrompt,
      messages: [{ role: "user", content: userMessage }],
      tools: availableActions,
    });

    // 5. Execute any actions
    if (response.toolCalls) {
      for (const call of response.toolCalls) {
        await this.actions.execute(call.name, call.arguments);
      }
    }

    return response.text;
  }

  buildPrompt(ctx: Context): string {
    return `You are a copilot for ${ctx.app.name}.

User: ${ctx.user.name} (${ctx.user.role})
Current page: ${ctx.page.title} (${ctx.page.route})
Recent actions: ${ctx.recentActions.map(a => a.description).join(", ")}

Available data on this page:
${JSON.stringify(ctx.data, null, 2)}

User preferences: ${JSON.stringify(ctx.preferences)}

You can help the user by:
- Answering questions about their data
- Suggesting actions
- Creating, updating, or deleting items
- Navigating to different pages
- Providing tutorials and help

Be concise, helpful, and proactive. If you notice the user struggling,
offer specific suggestions.`;
  }
}

Proactive Suggestions

class ProactiveCopilot:
    """Copilot that suggests actions before the user asks."""

    async def check_for_suggestions(self, user_id, current_page):
        context = await self.get_context(user_id, current_page)

        suggestions = []

        # Detect confusion patterns
        if await self.is_user_stuck(context):
            suggestions.append({
                "type": "help",
                "message": "It looks like you might be stuck. Want a quick tour?",
                "action": "start_tutorial",
            })

        # Detect incomplete setup
        if await self.has_incomplete_setup(context):
            suggestions.append({
                "type": "setup",
                "message": "Your profile is 60% complete. Want to finish it?",
                "action": "navigate_to_settings",
            })

        # Detect optimization opportunities
        if await self.can_optimize(context):
            suggestions.append({
                "type": "optimization",
                "message": "I noticed you could save 3 hours/week by automating reports.",
                "action": "show_automation_guide",
            })

        return suggestions

    async def is_user_stuck(self, context):
        """Detect if user seems confused."""
        # Patterns: repeated visits to help, slow progress, back-and-forth
        return (
            context.recent_actions.count("visit_help") > 2 or
            context.time_on_page > 120 and context.actions_on_page == 0
        )

Copilot UI Patterns

Side Panel

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚      Main Application       β”‚ Copilot β”‚
β”‚                             β”‚         β”‚
β”‚   [App Content]            β”‚ Chat    β”‚
β”‚                             β”‚ history β”‚
β”‚                             β”‚         β”‚
β”‚                             β”‚ β”Œβ”€β”€β”€β”€β”€β” β”‚
β”‚                             β”‚ β”‚Inputβ”‚ β”‚
β”‚                             β”‚ β””β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Inline Suggestions

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Email Composer              β”‚
β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚ β”‚ Subject: [          ] βœ¨β”‚ β”‚
β”‚ β”‚   ↑ "Quarterly Review?" β”‚ β”‚
β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Action Bar

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Copilot: "I can help with:" β”‚
β”‚ [Schedule] [Analyze] [Share]β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Security for Copilots

class SecureCopilot extends Copilot {
  // Verify user can perform the action
  async execute(actionName: string, params: any, userId: string) {
    // Check permissions
    const hasPermission = await this.rbac.check(
      userId,
      actionName,
      params.resource,
    );

    if (!hasPermission) {
      return "You don't have permission to do that.";
    }

    // Sanitize params
    const sanitized = this.sanitize(params);

    // Execute
    return await super.execute(actionName, sanitized);
  }
}

Measuring Copilot Success

Metric Target How to Measure
Usage rate > 40% of users Active copilot users / total users
Tasks completed via copilot > 15% Copilot-triggered actions / total
User satisfaction > 4/5 Post-interaction rating
Time saved Measurable Compare task time with/without copilot
Deflection rate > 30% Support tickets avoided via copilot

Publishing Copilot Skills

Build copilot functionality as MCP skills that others can use:

# A copilot skill for any application
@mcp_tool(
    name="form-filler",
    description="Intelligently fill forms based on user data and context",
)
async def fill_form(form_schema, user_data):
    filled = await ai.fill_form(form_schema, user_data)
    return {"filled_form": filled, "confidence": 0.95}

Publish on SkillExchange so other applications can integrate your copilot skill.


Conclusion

AI copilots represent the next evolution of user interfaces β€” from passive tools to active assistants that understand context, take actions, and proactively help users succeed.

By building copilots with strong context awareness, action capabilities, and proactive suggestions, you can dramatically improve user experience and productivity in your application.


Learn More

Build copilots with skills from SkillExchange.

Newsletter

Enjoying this article?

Get weekly insights on building and selling AI skills, MCP tools, and creator economics. Join 2,000+ AI builders and creators.

No spam. Unsubscribe anytime.

Related Articles

Ready to try AI skills?

Browse the marketplace and discover skills for your AI agents.

Browse Skills