This guide walks you through the process of adding new AI agents to your Agentic Templates application.

Implementation Guide

Follow these steps to implement a new agent in your application.

1. Implement the Agent UI

Start with the agent configuration in the dashboard repository. This is where you’ll define how your agent appears and behaves in the UI.

Create the agent configuration in lib/agents/your-agent.agent.ts

// src/lib/agents/your-agent.agent.ts
import type { AgentConfig } from '@/schemas';

export const yourAgent: AgentConfig = {
    id: 'unique-uuid-for-your-agent',
    name: 'Your Agent Name',
    title: 'Your Agent Title',
    description: 'Description of what your agent does',
    category: 'your-category',
    imageUrl: 'https://your-image-url.com',
    credits: 20, // Cost in credits to use this agent
    available: true,
    keyDeliverables: [
        'Key deliverable 1',
        'Key deliverable 2'
    ],
    packageDescription: [
        'One Page Deliverable'
    ],
    estimatedDelivery: 'Less than 1 min',
    fields: {
        deliverableName: {
            type: 'text',
            label: 'Deliverable Name',
            required: true,
            placeholder: 'Enter your deliverable name'
        },
        // Add your custom fields here
        field1: {
            type: 'text',
            label: 'Field 1',
            required: true,
            placeholder: 'Enter field 1'
        }
    },
    deliverable: {
        sections: [
            {
                id: 'section1',
                label: 'Section 1',
                type: 'text',
                description: 'Description of section 1',
                order: 1
            }
        ],
        availableFormats: ['pdf', 'markdown'],
    },
    handler: async (token: string, data: OrderFormData) => {
        const response = await agentService.handleAgentRequest(
            token,
            data.payload.formData
        );
        return response;
    }
};

Update the index file in lib/agents/index.ts

// src/lib/agents/index.ts
import { yourAgent } from './your-agent.agent';

export const agents = {
    yourAgent,
    // ... other agents
} as const;

Update schemas/agent.ts with the input types for the agent

// src/schemas/agent.ts
import { z } from 'zod';

export const YourAgentInputSchema = z.object({
    field1: z.string(),
    // ... other fields
});

export type YourAgentInput = z.infer<typeof YourAgentInputSchema>;

Update schemas/api.ts with the response body expected from the API

// src/schemas/api.ts
import { z } from 'zod';

export const YourAgentResponseSchema = z.object({
    orderId: z.string(),
    orderStatus: z.string(),
    orderCreatedAt: z.string(),
    deliverableName: z.string()
});

export type YourAgentResponse = z.infer<typeof YourAgentResponseSchema>;

Add the handler to services/agentService.ts

// src/services/agentService.ts
import { YourAgentInput, YourAgentResponse } from '@/schemas';
import { api } from './api';

export const agentService = {
    handleAgentRequest: async (
        token: string,
        input: YourAgentInput
    ): Promise<YourAgentResponse> => {
        return await api.post('/your-agent', input, token);
    }
};

Implement the client side api for the handler in services/api.ts

// src/services/api.ts
export const api = {
    post: async <T>(
        endpoint: string,
        data: any,
        token: string
    ): Promise<T> => {
        const response = await fetch(`${API_URL}${endpoint}`, {
            method: 'POST',
            headers: {
                'Content-Type': 'application/json',
                'Authorization': `Bearer ${token}`
            },
            body: JSON.stringify(data)
        });
        return response.json();
    }
};

2. Implement the API Endpoints

2.1 Update the infrastructure to support the new agent.

Update the infra/api.ts file

// infra/api.ts
api.route("POST /your-agent", {
    link: [...apiResources],
    handler: "./packages/functions/src/orchestrator.api.handleRequestYourAgent",
    timeout: "900 seconds"
});

Update the infra/queues.ts file

// infra/queues.ts
export const yourAgentQueue = new sst.aws.Queue("YourAgentQueue", {
    fifo: true
});   

yourAgentQueue.subscribe({
    handler: "./packages/functions/src/agent-plane.api.yourAgentHandler", 
    link: [
        deliverablesTable, 
        ordersTable, 
        openaiApiKey, 
    ], 
    permissions: [
        {
            actions: ["dynamodb:*"], 
            resources: [deliverablesTable.arn, ordersTable.arn]
        }
    ],
    timeout: "10 minutes"
});

Update the infra/topic.ts file

// infra/topic.ts
orderTopic.subscribeQueue(
    "yourAgent", 
    yourAgentQueue.arn, 
    {
        filter: {
            "queue": ["yourAgent"]
        }
    }
);

2.2 Update the orchestrator to route the request to the order topic

Update the metadata/agent-plane.schema.ts file with inputs types

// packages/core/src/orchestrator/metadata/agent-plane.schema.ts
import { z } from 'zod';

export const YourAgentSchema = z.object({
    sections: z.object({
        section1: z.object({
            id: z.string(),
            label: z.string(),
            type: z.literal('text'),
            description: z.string().optional(),
            data: z.string(),
        })
    })
});

export const RequestAgentInputSchema = BasePayloadSchema.extend({
    field1: z.string(),
});

export type RequestAgentInput = z.infer<typeof RequestAgentInputSchema>;

Update the metadata/order.enum.ts file with the cost of the agent

// packages/core/src/orchestrator/metadata/order.enum.ts
export enum AgentCost {
    YourAgent = 20,
}

Write the primary adapter in adapters/primary/your-agent-action.adapter.ts

// packages/core/src/orchestrator/adapters/primary/request-your-agent.adapter.ts
import { APIGatewayProxyEventV2 } from 'aws-lambda';
import { ValidUser } from '@utils/metadata/saas-identity.schema';

export const requestYourAgentAdapter = async (event: APIGatewayProxyEventV2) => {
    try {
        // Validate user authentication
        const validUser: ValidUser = await validateUser(event);

        // Parse and validate request body
        const { field1 } = JSON.parse(event.body);
        
        // Create unique IDs for order and deliverable
        const orderId = randomUUID();
        const deliverableId = randomUUID();

        // Validate input against schema
        const parsedInput = RequestAgentInputSchema.parse({
            userId: validUser.userId,
            orderId,
            deliverableId,
            field1
        });

        // Call use case and return response
        const result = await publishYourAgentUseCase(parsedInput);
        return successResponse(result);

    } catch (error) {
        return handleError(error);
    }
};

Write the usecase in usercase/your-agent-action.usecase.ts

// packages/core/src/orchestrator/usecases/request-your-agent.usecase.ts
export async function publishYourAgentUseCase(request: RequestAgentInput) {
    try {
        // Create order object
        const order = createOrder(request);

        // Save order to database
        await saveOrder(order);

        // Update user credits
        await updateCredits(request);

        // Publish order to SNS topic
        await publishOrder(order);

        return {
            orderId: order.orderId,
            status: 'pending'
        };
    } catch (error) {
        handleError(error);
    }
}

Create a function for the primary adapter in functions/src/orchestrator.api.ts

// packages/functions/src/orchestrator.api.ts
import { createHandler } from '@utils';
import { requestYourAgentAdapter } from '@adapters/primary/your-agent-action.adapter';

export const handleRequestYourAgent = createHandler(requestYourAgentAdapter);

2.3 Add a new agent module to the agent-plane

Create a new directory as agent-plane/your-agent

Create the following directory structure:

agent-plane/your-agent/
├── adapters/
│   ├── primary/
│   │   └── your-agent-action.adapter.ts
│   └── secondary/
│       ├── openai.adapter.ts
│       └── datastore.adapter.ts
├── metadata/
│   ├── your-agent.schema.ts
│   └── your-agent.prompt.ts
└── usecases/
    └── your-agent-action.usecase.ts

Implement the metadata for the agent in metadata/your-agent.schema.ts

// packages/core/src/agent-plane/your-agent/metadata/your-agent.schema.ts
import { z } from 'zod';

export const YourAgentSchema = z.object({
    sections: z.object({
        section1: z.object({
            id: z.string(),
            label: z.string(),
            type: z.literal('text'),
            description: z.string().optional(),
            data: z.string(),
        })
    })
});

export type YourAgentDeliverable = z.infer<typeof YourAgentSchema>;

Implement the prompt for the agent in metadata/your-agent.prompt.ts

// packages/core/src/agent-plane/your-agent/metadata/your-agent.prompt.ts
export const yourAgentSystemPrompt = (input: RequestAgentInput) => `
You are an expert in your domain.
Your task is to create a detailed one-page deliverable based on the provided input:
${JSON.stringify(input)}
Focus on providing actionable insights and recommendations.
`;

Implement the primary adapter in adapters/primary/your-agent-action.adapter.ts

// packages/core/src/agent-plane/your-agent/adapters/primary/your-agent-action.adapter.ts
import { SQSEvent } from 'aws-lambda';

export const yourAgentAdapter = async (event: SQSEvent) => {
    if (!event.Records?.length) {
        throw new Error("Missing SQS Records");
    }

    return Promise.all(event.Records.map(async (record) => {
          const message = parseMessage(record);
        const order = validateOrder(message);
        return await yourAgentUsecase(order);
    }));
};

Implement the usecase in usercase/your-agent-action.usecase.ts

// packages/core/src/agent-plane/your-agent/usecases/your-agent-action.usecase.ts
export const yourAgentUsecase = async (input: RequestAgentInput) => {
    try {
        // Generate content using OpenAI
        const content = await runAgent(input);

        // Create deliverable object
        const deliverable = createDeliverable(input, content);

        // Save deliverable to database
        await saveDeliverable(deliverable);

        return { message: 'Agent deliverable created successfully' };
    } catch (error) {
        handleError(error);
    }
};

Implement the secondary adapter in adapters/secondary/openai.adapter.ts

// packages/core/src/agent-plane/your-agent/adapters/secondary/openai.adapter.ts
import OpenAI from "openai";

export const yourAgentAdapter = async (input: RequestAgentInput) => {
    try {
        // Create OpenAI Assistant
        const assistant = await createAssistant(input);

        // Create Thread
        const thread = await createThread();

        // Run Assistant
        const run = await runAssistant(thread.id, assistant.id);

        // Get and validate response
        const content = await getValidatedResponse(thread.id);

        // Cleanup
        await cleanup(assistant.id, thread.id);

        return content;
    } catch (error) {
        handleError(error);
    }
};

export const runAgent = withRetry(yourAgentAdapter, {
    retries: 3,
    delay: 1000
});

Create a function for the primary adapter in functions/src/agent-plane.api.ts

// packages/functions/src/agent-plane.api.ts
import { createSQSHandler } from '@utils';
export const yourAgentHandler = createSQSHandler(yourAgentAdapter);

This completes the implementation of your new agent. The agent will now:

  1. Accept user input through the UI
  2. Validate and process the request through the API
  3. Generate content using OpenAI
  4. Store the deliverable in the database
  5. Return the result to the user

Remember to:

  • Test each component thoroughly
  • Handle errors appropriately
  • Follow the AMU pattern for clean architecture
  • Use proper typing and validation
  • Document your code
  • Add appropriate logging
  • Follow security best practices