Skip to main content
A complete customer support agent that searches your documentation, creates tickets, and tracks resolution metrics. This example shows the full project structure with separate files for tools and prompts.

Project Structure

support-agent/
├── main.ts
├── agent.ts
├── tools/
│   ├── index.ts
│   ├── create-ticket.ts
│   └── search-orders.ts
├── prompts/
│   └── index.ts
├── .runflow/
│   └── rf.json
├── package.json
└── tsconfig.json

Step 1: Define Your Prompt

Start with well-structured instructions that tell the agent how to behave, when to use tools, and how to respond.
prompts/index.ts
export const supportPrompt = `You are a customer support agent for ACME Corp.

## Behavior
- Always be professional, empathetic, and solution-oriented
- Respond in the customer's language
- Search the knowledge base before answering technical questions
- If you don't know something, say so honestly — never guess

## Tools
- Use **search-orders** when customers ask about order status, delivery, or tracking
- Use **create-ticket** when:
  - The issue cannot be resolved in this conversation
  - The customer needs a refund or account change
  - Always confirm with the customer before creating a ticket
  - Set priority: 'high' if customer is blocked, 'medium' for inconveniences, 'low' for feature requests

## Knowledge Base
- The knowledge base contains product documentation, FAQs, and policies
- Always search it when customers ask about features, pricing, or processes
- Quote relevant information when answering

## Response Format
- Be concise (2-3 paragraphs max)
- Use bullet points for step-by-step instructions
- Always confirm actions taken ("I've created ticket #123 for you")
- End with a follow-up question when appropriate`;

Step 2: Create Your Tools

Each tool lives in its own file with clear input validation and structured responses.
tools/search-orders.ts
import { createTool } from '@runflow-ai/sdk';
import { z } from 'zod';

export const searchOrdersTool = createTool({
  id: 'search-orders',
  description: 'Search customer orders by order ID or customer email',
  inputSchema: z.object({
    orderId: z.string().optional().describe('Order ID (e.g., ORD-12345)'),
    customerEmail: z.string().email().optional().describe('Customer email'),
  }),
  execute: async ({ context, runflow }) => {
    try {
      // Example: query your database or API
      const response = await fetch(
        `https://api.yourcompany.com/orders?id=${context.orderId || ''}&email=${context.customerEmail || ''}`,
        { headers: { 'Authorization': `Bearer ${process.env.ORDERS_API_KEY}` } }
      );

      if (!response.ok) {
        return { found: false, error: 'Failed to search orders' };
      }

      const orders = await response.json();

      if (!orders.length) {
        return { found: false, query: context.orderId || context.customerEmail };
      }

      return {
        found: true,
        orders: orders.map((o: any) => ({
          id: o.id,
          status: o.status,
          total: o.total,
          createdAt: o.createdAt,
          estimatedDelivery: o.estimatedDelivery,
        })),
      };
    } catch (error) {
      return { found: false, error: 'Service temporarily unavailable' };
    }
  },
});
tools/create-ticket.ts
import { createTool } from '@runflow-ai/sdk';
import { track } from '@runflow-ai/sdk/observability';
import { z } from 'zod';

export const createTicketTool = createTool({
  id: 'create-ticket',
  description: 'Create a support ticket for issues that need human follow-up',
  inputSchema: z.object({
    subject: z.string().describe('Brief description of the issue'),
    description: z.string().describe('Detailed description with context'),
    priority: z.enum(['low', 'medium', 'high']).describe('Issue priority'),
  }),
  execute: async ({ context, runflow }) => {
    try {
      // Example: create ticket via connector or API
      const ticket = await runflow.connector('hubspot', 'create-ticket', {
        subject: context.subject,
        content: context.description,
        priority: context.priority,
      });

      // Track for business metrics
      track('ticket_created', {
        priority: context.priority,
        subject: context.subject,
      });

      return {
        success: true,
        ticketId: ticket.id,
        message: `Ticket ${ticket.id} created successfully`,
      };
    } catch (error) {
      return {
        success: false,
        error: 'Failed to create ticket. Please try again.',
      };
    }
  },
});
Re-export everything from an index file:
tools/index.ts
export { searchOrdersTool } from './search-orders';
export { createTicketTool } from './create-ticket';

Step 3: Configure the Agent

agent.ts
import { Agent, openai } from '@runflow-ai/sdk';
import { supportPrompt } from './prompts';
import { searchOrdersTool, createTicketTool } from './tools';

export const supportAgent = new Agent({
  name: 'Customer Support',
  instructions: supportPrompt,
  model: openai('gpt-4o'),

  memory: {
    maxTurns: 20,
    summarizeAfter: 15,
    summarizePrompt: 'Summarize key issues, actions taken, and pending items',
  },

  rag: {
    vectorStore: 'support-docs',
    k: 5,
    threshold: 0.7,
    searchPrompt: `Search the knowledge base when the customer asks about:
- Product features or how things work
- Pricing or plan details
- Policies (refund, cancellation, etc.)
- Technical troubleshooting steps`,
  },

  tools: {
    searchOrders: searchOrdersTool,
    createTicket: createTicketTool,
  },

  observability: 'full',
});

Step 4: Wire Everything in main.ts

main.ts
import { identify, track } from '@runflow-ai/sdk/observability';
import { supportAgent } from './agent';

export async function main(input: any) {
  // Validate input
  if (!input?.message || typeof input.message !== 'string') {
    return { error: 'message is required' };
  }

  // Identify the customer
  identify(input.email || input.phone || input.userId || 'anonymous');

  try {
    const result = await supportAgent.process({
      message: input.message,
      sessionId: input.sessionId,
    });

    // Track support metrics
    track('support_request', {
      channel: input.channel || 'api',
      resolved: !result.metadata?.toolsUsed?.includes('create-ticket'),
    });

    return {
      message: result.message,
      metadata: result.metadata,
    };
  } catch (error) {
    console.error('[support-agent] Error:', error);
    return { error: 'An error occurred. Please try again.' };
  }
}

Setting Up the Knowledge Base

Upload your documentation to power RAG:
# Create the knowledge base
rf kb create support-docs

# Upload files
rf kb upload support-docs ./docs/faq.md
rf kb upload support-docs ./docs/pricing.md
rf kb upload support-docs ./docs/refund-policy.md

# Or upload an entire directory
rf kb upload support-docs ./docs/

# Test search
rf kb search support-docs "How do I cancel my subscription?"

Key Takeaways

  • Separate concerns: tools, prompts, and agent config in their own files
  • Always identify: call identify() before agent.process() for proper memory and tracing
  • Track metrics: use track() in tools and main.ts to power dashboards
  • Handle errors: validate input, try/catch in main, return error objects from tools
  • Write specific instructions: tell the agent exactly when and how to use each tool
  • Use RAG wisely: write a specific searchPrompt so the agent knows when to search

Next Steps

Collections Agent

WhatsApp collections example

Knowledge (RAG)

Learn more about RAG

Best Practices

Tips for effective agents

Project Structure

Organize your project