Skip to main content
Every Runflow project follows a simple convention: a main.ts file at the root that exports an async function main(). As your project grows, you organize code into folders that map directly to SDK concepts.

Entry Point: main.ts

The main.ts file is the only required file. It must export an async function main(input) — this is the contract between your code and the Runflow engine.
main.ts
import { Agent, openai } from '@runflow-ai/sdk';
import { identify } from '@runflow-ai/sdk/observability';

const agent = new Agent({
  name: 'My Agent',
  instructions: '...',
  model: openai('gpt-4o'),
});

export async function main(input: any) {
  identify(input.email || input.phone);

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

  return { message: result.message };
}
What main() receives:
  • input.message — the user’s message (always present)
  • input.sessionId — session identifier (for memory continuity)
  • input.email, input.phone — user identifiers (depends on your integration)
  • Any other fields your integration sends
What main() returns:
  • An object with at least message — the agent’s response
  • Any additional metadata you want to pass back

Project Sizes

Simple Project

For a basic agent with one or two tools, keep everything minimal:
my-agent/
├── main.ts              # Agent + main function
├── tools/
│   └── weather.ts       # One file per tool
├── .runflow/
│   └── rf.json
├── package.json
└── tsconfig.json

Medium Project

When you have multiple tools and want better organization:
my-agent/
├── main.ts              # Entry point + orchestration
├── agent.ts             # Agent definition (separated from main)
├── tools/
│   ├── index.ts         # Re-exports all tools
│   ├── create-ticket.ts
│   ├── search-orders.ts
│   └── send-email.ts
├── prompts/
│   └── index.ts         # System prompt + templates
├── .runflow/
│   └── rf.json
├── package.json
└── tsconfig.json

Complex Project

For enterprise agents with workflows, connectors, and integrations:
my-agent/
├── main.ts              # Entry point + routing logic
├── agent.ts             # Agent definition
├── tools/
│   ├── index.ts
│   ├── create-ticket.ts
│   ├── classify-intent.ts
│   └── send-notification.ts
├── prompts/
│   └── index.ts         # System prompt + scenario prompts
├── workflows/
│   └── lead-qualification.ts
├── connectors/
│   └── hubspot.ts       # Connector configurations
├── config/
│   └── settings.ts      # Constants, enums, configurations
├── .runflow/
│   └── rf.json
├── package.json
└── tsconfig.json

Folder Guide

tools/

One file per tool. Each file exports a single tool created with createTool().
tools/create-ticket.ts
import { createTool } from '@runflow-ai/sdk';
import { z } from 'zod';

export const createTicketTool = createTool({
  id: 'create-ticket',
  description: 'Create a support ticket in the system',
  inputSchema: z.object({
    subject: z.string(),
    description: z.string(),
    priority: z.enum(['low', 'medium', 'high']),
  }),
  execute: async ({ context }) => {
    // Your logic here
    return { ticketId: 'TICKET-123', success: true };
  },
});
Use an index.ts to re-export all tools:
tools/index.ts
export { createTicketTool } from './create-ticket';
export { searchOrdersTool } from './search-orders';
export { sendEmailTool } from './send-email';
Then import them cleanly in your agent:
agent.ts
import { createTicketTool, searchOrdersTool, sendEmailTool } from './tools';

const agent = new Agent({
  tools: {
    createTicket: createTicketTool,
    searchOrders: searchOrdersTool,
    sendEmail: sendEmailTool,
  },
});

prompts/

Centralize your prompts in a dedicated file, especially when they’re long or have multiple scenarios:
prompts/index.ts
export const systemPrompt = `You are a customer support agent for ACME Corp.

## Behavior
- Always be professional and empathetic
- Respond in the customer's language
- Search the knowledge base before answering technical questions

## Tools
- Use create-ticket for issues that need human follow-up
- Use search-orders when customers ask about their orders
- Use send-email to notify the team about urgent issues

## Response Format
- Be concise but complete
- Use bullet points for lists
- Always confirm actions taken`;

export const escalationPrompt = `A customer issue needs escalation.
Customer: {{customerName}}
Issue: {{issue}}
Priority: {{priority}}

Write a brief summary for the support team.`;
For prompts managed through the Runflow portal, use loadPrompt() instead of local files. See Prompts for details.

workflows/

Store workflow definitions in dedicated files:
workflows/lead-qualification.ts
import { createWorkflow } from '@runflow-ai/sdk';
import { z } from 'zod';

export const leadQualificationWorkflow = createWorkflow({
  id: 'lead-qualification',
  inputSchema: z.object({
    email: z.string().email(),
    company: z.string(),
    notes: z.string(),
  }),
  // ... workflow steps
});

connectors/

When using multiple connectors, configure them in dedicated files:
connectors/hubspot.ts
import { createConnectorTool } from '@runflow-ai/sdk';

export const createContactTool = createConnectorTool('hubspot', 'create_contact');
export const updateDealTool = createConnectorTool('hubspot', 'update_deal');

config/

For projects with many constants, enums, or configuration values:
config/settings.ts
export const PRIORITIES = ['low', 'medium', 'high', 'urgent'] as const;
export type Priority = (typeof PRIORITIES)[number];

export const AGENT_CONFIG = {
  model: 'gpt-4o',
  temperature: 0,
  maxTokens: 3000,
  memoryMaxTurns: 50,
};

Separating the Agent from main.ts

As your project grows, move the agent definition to its own file. This keeps main.ts focused on orchestration:
agent.ts
import { Agent, openai } from '@runflow-ai/sdk';
import { systemPrompt } from './prompts';
import { createTicketTool, searchOrdersTool } from './tools';

export const supportAgent = new Agent({
  name: 'Support Agent',
  instructions: systemPrompt,
  model: openai('gpt-4o'),
  memory: { maxTurns: 20 },
  tools: {
    createTicket: createTicketTool,
    searchOrders: searchOrdersTool,
  },
  observability: 'full',
});
main.ts
import { identify, track } from '@runflow-ai/sdk/observability';
import { supportAgent } from './agent';

export async function main(input: any) {
  identify(input.email || input.phone);

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

  track('support_request', {
    channel: input.channel,
    resolved: !result.metadata?.toolsUsed?.includes('createTicket'),
  });

  return { message: result.message };
}

Next Steps