Learning Paths & Tutorials

AI Agents

Build typed, sandboxed LLM agents.

This tutorial walks you through building a PURISTA AI agent that classifies support tickets, calls business commands as tools, and returns structured, validated output.

AI agents in PURISTA are first-class citizens of the message model. They run as queue-driven workers — they consume a job from a queue bridge, interact with LLM models and tool commands, and return a validated structured result. Like any other service component, they are typed with Zod schemas, sandboxed through the context object, and testable without a running LLM.

What you will build

A triage agent that:

  1. Receives a support ticket (text)
  2. Uses an LLM to classify priority (low/normal/high)
  3. Calls a business command to fetch customer tier
  4. Returns a structured result with priority and reason

Prerequisites

  • PURISTA project with @purista/core installed
  • @purista/ai-harness installed (required for getAgentQueueBuilder support)
  • An AI provider package installed (e.g. @purista/harness-openai)

Step 1: Create the service

import { ServiceBuilder } from '@purista/core'

export const supportV1ServiceBuilder = new ServiceBuilder({
  serviceName: 'support',
  serviceVersion: '1',
  serviceDescription: 'Support workflows',
})

Step 2: Define the agent

const triageAgent = supportV1ServiceBuilder
  .getAgentQueueBuilder('triage', 'Classify incoming support tickets')
  .addPayloadSchema(z.object({
    ticketId: z.string(),
    text: z.string(),
  }))
  .addOutputSchema(z.object({
    priority: z.enum(['low', 'normal', 'high']),
    reason: z.string(),
  }))

Step 3: Add a model

.addModel('primary', {
  model: 'gpt-4.1-mini',
  capabilities: ['object'],
  defaults: {
    temperature: 0,
    maxTokens: 1200,
  },
})

Step 4: Add a command tool

Allow the agent to call a business command:

.canInvoke('customer', '1', 'getProfile', {
  payloadSchema: z.object({ customerId: z.string() }),
  outputSchema: z.object({
    tier: z.enum(['standard', 'enterprise']),
    openIncidents: z.number(),
  }),
})

Step 5: Implement the run function

.setRunFunction(async function(context, payload) {
  // Fetch customer profile using the tool
  const profile = await context.invoke.tools['customer.1.getProfile'].call({
    customerId: payload.customerId,
  })

  // Ask the model to classify
  const result = await context.harness.models.primary.object({
    messages: [{
      role: 'user',
      content: `Customer tier: ${profile.tier}, Open incidents: ${profile.openIncidents}. Ticket: ${payload.text}`,
    }],
    schema: {
      type: 'object',
      properties: {
        priority: { enum: ['low', 'normal', 'high'] },
        reason: { type: 'string' },
      },
      required: ['priority', 'reason'],
    },
  }, context.signal)

  return result.object
})

Step 6: Add to service and start

Assemble the service from the builder by registering the agent definition, then get a runtime instance by passing the event bridge and configuration:

import { supportV1ServiceBuilder } from './supportV1ServiceBuilder.js'

export const supportV1Service = supportV1ServiceBuilder
  .addAgentDefinition(await triageAgent.getDefinition())

const supportService = await supportV1Service.getInstance(eventBridge, {
  queueBridge,
  ai: {
    models: {
      primary: {
        provider: openaiProvider,
        model: 'gpt-4.1-mini',
        capabilities: ['object'],
      },
    },
  },
})

Step 7: Test

const result = await context.service.support['1'].triage({
  ticketId: 'TICKET-123',
  text: 'Cannot log in to the dashboard',
}, {})

console.log(result.priority) // 'high' or 'normal' or 'low'
console.log(result.reason)   // 'Customer is enterprise with open incidents'

What you learned

  • Agents are queue-driven workflows with typed schemas
  • Models are declared with capabilities and bound at runtime
  • Tools are allowlisted business commands with typed schemas
  • context.signal enables cancellation propagation
  • Outputs are validated against Zod schemas

Next steps

Related

Read Next
Philosophy

from Mental Model & Philosophy