Examples
Practical examples of using the Agentuity JavaScript SDK
This section provides practical examples of using the Agentuity JavaScript SDK for common use cases. The examples are organized from beginner to advanced, progressively introducing features and patterns.
Getting Started
Hello World Agent
Here's a basic agent that processes requests and returns responses:
import { createAgent } from '@agentuity/runtime';
const agent = createAgent('Hello', {
handler: async (ctx, input) => {
ctx.logger.info('Received request');
return {
message: 'Hello, World!',
timestamp: new Date().toISOString()
};
}
});
export default agent;Key Points:
- Direct return values (no response object needed)
- Input is automatically available
- Logger accessed via
ctx.logger - Returns plain JavaScript objects
Type-Safe Agent with Validation
This example demonstrates schema validation for runtime type safety and automatic validation:
import { createAgent } from '@agentuity/runtime';
import { z } from 'zod';
const agent = createAgent('UserRegistration', {
schema: {
input: z.object({
email: z.string().email(),
age: z.number().min(18)
}),
output: z.object({
userId: z.string().uuid(),
status: z.enum(['active', 'pending'])
})
},
handler: async (ctx, input) => {
// input.email and input.age are fully typed and validated
return {
userId: crypto.randomUUID(),
status: 'active'
};
}
});
export default agent;Key Points:
- Schemas provide automatic validation and type inference
- Input validation happens before handler execution
- Output validation ensures consistent response shape
- TypeScript provides full autocomplete
For advanced schema validation patterns including custom validation rules, transformations, error handling, and using alternative libraries like Valibot or ArkType, see the Schema Validation Guide.
Agent with Structured Logging
This example demonstrates structured logging for monitoring and debugging:
import { createAgent } from '@agentuity/runtime';
const agent = createAgent('LoggingExample', {
handler: async (ctx, input) => {
// Different log levels
ctx.logger.info('Processing request', {
sessionId: ctx.sessionId,
inputSize: JSON.stringify(input).length
});
ctx.logger.debug('Detailed processing info', { data: input });
try {
const result = await processData(input);
ctx.logger.info('Processing successful', {
resultSize: JSON.stringify(result).length
});
return result;
} catch (error) {
ctx.logger.error('Processing failed', {
error: error.message,
stack: error.stack
});
throw error;
}
}
});
async function processData(input: any) {
// Simulate processing
return {
processed: true,
data: input,
timestamp: Date.now()
};
}
export default agent;Key Points:
- Use structured logging with metadata objects
- Available log levels:
trace,debug,info,warn,error,fatal - Logs are automatically indexed and searchable
- Include context like
sessionIdfor request tracing
Core Features
Key-Value Storage
This example demonstrates how to use the key-value storage API for data persistence:
import { createAgent } from '@agentuity/runtime';
import * as v from 'valibot';
const agent = createAgent('PreferenceManager', {
schema: {
input: v.object({
action: v.union([
v.literal('get'),
v.literal('set'),
v.literal('delete')
]),
userId: v.string(),
preferences: v.optional(v.any())
}),
output: v.object({
message: v.optional(v.string()),
preferences: v.optional(v.any()),
success: v.boolean()
})
},
handler: async (ctx, input) => {
const { action, userId, preferences } = input;
if (action === 'get') {
const result = await ctx.kv.get('user-preferences', userId);
if (!result.exists) {
return { success: false, message: 'No preferences found' };
}
const userPrefs = await result.data.json();
return { success: true, preferences: userPrefs };
}
if (action === 'set') {
await ctx.kv.set(
'user-preferences',
userId,
preferences,
{ ttl: 60 * 60 * 24 * 30 } // 30 days in seconds
);
return { success: true, message: 'Preferences saved' };
}
// Delete
await ctx.kv.delete('user-preferences', userId);
return { success: true, message: 'Preferences deleted' };
}
});
export default agent;Key Points:
ctx.kv.get()returns a result withexistsflagctx.kv.set()supports optional TTL for expiring data- Data can be stored as JSON objects or strings
- Storage is namespaced by the first parameter
Vector Search & Semantic Retrieval
This example demonstrates how to use vector storage for semantic search:
import { createAgent } from '@agentuity/runtime';
import { type } from 'arktype';
const ProductType = type({
id: 'string',
name: 'string',
description: 'string',
price: 'number',
category: 'string'
});
const agent = createAgent('ProductCatalog', {
schema: {
input: type({
action: '"index"|"search"|"delete"',
query: 'string?',
products: type({ array: ProductType }).optional()
}),
output: type({
message: 'string?',
ids: 'string[]?',
results: 'unknown[]?',
count: 'number?',
deletedCount: 'number?'
})
},
handler: async (ctx, input) => {
const { action, query, products } = input;
if (action === 'index') {
if (!products || products.length === 0) {
throw new Error('No products to index');
}
const documents = products.map(product => ({
key: product.id,
document: product.description,
metadata: {
id: product.id,
name: product.name,
price: product.price,
category: product.category
}
}));
const ids = await ctx.vector.upsert('products', ...documents);
return {
message: `Indexed ${ids.length} products`,
ids
};
}
if (action === 'search') {
if (!query) {
throw new Error('Query is required');
}
const results = await ctx.vector.search('products', {
query,
limit: 5,
similarity: 0.7
});
const formatted = results.map(result => ({
id: result.id,
key: result.key,
...result.metadata,
similarity: result.similarity
}));
return {
results: formatted,
count: formatted.length
};
}
// Delete
if (!products || products.length === 0) {
throw new Error('No product IDs to delete');
}
const productIds = products.map(p => p.id);
const deletedCount = await ctx.vector.delete('products', ...productIds);
return {
message: `Deleted ${deletedCount} product(s)`,
deletedCount
};
}
});
export default agent;Key Points:
- ArkType provides concise TypeScript-first syntax
- Vector upsert stores documents with metadata
- Search supports similarity threshold and result limits
- Metadata filtering available via search params
Object Storage with Public URLs
Using object storage for files and generating public access URLs:
import { createAgent } from '@agentuity/runtime';
import { s3 } from 'bun';
import * as v from 'valibot';
const agent = createAgent('FileManager', {
schema: {
input: v.object({
action: v.union([
v.literal('upload'),
v.literal('download'),
v.literal('share'),
v.literal('delete')
]),
filename: v.string(),
data: v.optional(v.any()),
expiresIn: v.optional(v.number())
}),
output: v.object({
success: v.boolean(),
url: v.optional(v.string()),
data: v.optional(v.any()),
message: v.optional(v.string())
})
},
handler: async (ctx, input) => {
const { action, filename, data, expiresIn } = input;
if (action === 'upload') {
const file = s3.file(`documents/${filename}`);
await file.write(data);
return {
success: true,
message: 'File uploaded successfully'
};
}
if (action === 'download') {
const file = s3.file(`documents/${filename}`);
const exists = await file.exists();
if (!exists) {
return {
success: false,
message: 'File not found'
};
}
return {
success: true,
data: await file.text()
};
}
if (action === 'share') {
const url = s3.presign(`documents/${filename}`, {
expiresIn: Math.floor((expiresIn || 3600000) / 1000), // Convert to seconds
});
return {
success: true,
url,
message: `URL expires in ${expiresIn || 3600000}ms`
};
}
// Delete
const file = s3.file(`documents/${filename}`);
await file.delete();
return {
success: true,
message: 'File deleted'
};
}
});
export default agent;Key Points:
- Store files with metadata and content type
- Generate time-limited public URLs for secure sharing
- Support for custom content types and metadata
- Delete returns boolean indicating if file existed
Agent-to-Agent Communication
Calling other agents to build workflows:
import { createAgent } from '@agentuity/runtime';
import enrichmentAgent from '@agent/enrichment';
import analyzerAgent from '@agent/analyzer';
import categorizerAgent from '@agent/categorizer';
import processorAgent from '@agent/processor';
const agent = createAgent('Workflow', {
handler: async (ctx, input) => {
ctx.logger.info('Starting multi-agent workflow');
// Call enrichment agent
const enriched = await enrichmentAgent.run({
text: input.text
});
// Call multiple agents in parallel
const [analyzed, categorized] = await Promise.all([
analyzerAgent.run({ data: enriched }),
categorizerAgent.run({ data: enriched })
]);
// Call final processing agent with combined results
const final = await processorAgent.run({
analyzed,
categorized,
original: input
});
return {
processed: true,
results: final
};
}
});
export default agent;Key Points:
- Import and call agents:
import agent from '@agent/name'; agent.run() - Type-safe when agents have schemas
- Supports parallel execution with
Promise.all() - Results automatically validated against output schemas
RAG Agent
Complete RAG (Retrieval-Augmented Generation) pattern:
import { createAgent } from '@agentuity/runtime';
import { z } from 'zod';
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';
const agent = createAgent('RAG', {
schema: {
input: z.object({
question: z.string()
}),
output: z.object({
answer: z.string(),
sources: z.array(z.string()),
confidence: z.number()
})
},
handler: async (ctx, input) => {
// Search vector database for relevant context
const results = await ctx.vector.search('knowledge-base', {
query: input.question,
limit: 3,
similarity: 0.7
});
if (results.length === 0) {
return {
answer: 'I could not find relevant information to answer your question.',
sources: [],
confidence: 0
};
}
// Build context from search results
const context = results
.map((r, i) => `[${i + 1}] ${r.metadata?.text}`)
.join('\n\n');
// Generate answer using LLM
const { text } = await generateText({
model: openai('gpt-5-nano'),
prompt: `Answer the question based only on the provided context.
Context:
${context}
Question: ${input.question}
Provide a clear, concise answer citing the source numbers when appropriate.`
});
// Calculate confidence from average similarity
const avgSimilarity = results.reduce((sum, r) => sum + r.similarity, 0) / results.length;
return {
answer: text,
sources: results.map(r => r.id),
confidence: avgSimilarity
};
}
});
export default agent;Key Points:
- Combines vector search with LLM generation
- Provides source citations for verification
- Confidence score based on retrieval quality
- Graceful handling of no-results case
Error Handling Patterns
Comprehensive error handling with custom error classes:
import { createAgent } from '@agentuity/runtime';
import { z } from 'zod';
// Custom error classes
class ValidationError extends Error {
constructor(message: string) {
super(message);
this.name = 'ValidationError';
}
}
class ResourceNotFoundError extends Error {
constructor(resourceId: string) {
super(`Resource not found: ${resourceId}`);
this.name = 'ResourceNotFoundError';
}
}
const agent = createAgent('ResourceProcessor', {
schema: {
input: z.object({
resourceId: z.string()
}),
output: z.object({
message: z.string(),
result: z.any().optional(),
error: z.string().optional()
})
},
handler: async (ctx, input) => {
try {
ctx.logger.info(`Processing resource: ${input.resourceId}`);
// Simulate resource lookup
const resource = await lookupResource(input.resourceId, ctx);
// Process the resource
const result = await processResource(resource, ctx);
return {
message: 'Resource processed successfully',
result
};
} catch (error) {
// Handle different error types
if (error instanceof ValidationError) {
ctx.logger.warn(`Validation error: ${error.message}`);
return {
error: 'Validation error',
message: error.message
};
}
if (error instanceof ResourceNotFoundError) {
ctx.logger.warn(`Resource not found: ${error.message}`);
return {
error: 'Resource not found',
message: error.message
};
}
// Handle unexpected errors
ctx.logger.error('Unexpected error', error);
return {
error: 'Internal server error',
message: 'An unexpected error occurred'
};
}
}
});
// Helper functions
async function lookupResource(resourceId: string, ctx: any) {
const result = await ctx.kv.get('resources', resourceId);
if (!result.exists) {
throw new ResourceNotFoundError(resourceId);
}
return result.data;
}
async function processResource(resource: any, ctx: any) {
ctx.logger.debug('Processing resource', resource);
return {
id: resource.id,
status: 'processed',
timestamp: new Date().toISOString()
};
}
export default agent;Key Points:
- Custom error classes for specific scenarios
- Structured error handling with try/catch
- Different responses for different error types
- Comprehensive logging at appropriate levels
Advanced Patterns
Session & Thread State Management
Managing state at three different scopes:
import { createAgent } from '@agentuity/runtime';
import { z } from 'zod';
const agent = createAgent('StateManager', {
schema: {
input: z.object({
message: z.string(),
resetThread: z.boolean().optional()
}),
output: z.object({
response: z.string(),
stats: z.object({
requestDuration: z.number(),
threadMessages: z.number(),
sessionTotal: z.number()
})
})
},
handler: async (ctx, input) => {
// Request-scoped state (cleared after request)
ctx.state.set('startTime', Date.now());
// Thread-scoped state (conversation context)
if (input.resetThread) {
await ctx.thread.destroy();
}
const threadMessages = (ctx.thread.state.get('messageCount') as number) || 0;
ctx.thread.state.set('messageCount', threadMessages + 1);
const messages = (ctx.thread.state.get('messages') as string[]) || [];
messages.push(input.message);
ctx.thread.state.set('messages', messages);
// Session-scoped state (user-level, spans threads)
const sessionTotal = (ctx.session.state.get('totalRequests') as number) || 0;
ctx.session.state.set('totalRequests', sessionTotal + 1);
ctx.session.state.set('lastActive', Date.now());
return {
response: `Processed message ${threadMessages + 1} in this conversation`,
stats: {
requestDuration: Date.now() - (ctx.state.get('startTime') as number),
threadMessages: threadMessages + 1,
sessionTotal: sessionTotal + 1
}
};
}
});
export default agent;Key Points:
- Request state: Temporary data within a single request
- Thread state: Conversation context across multiple requests
- Session state: User-level data spanning multiple threads
- Thread can be destroyed to reset conversation context
Event System & Lifecycle Hooks
Monitoring agent execution with events:
import { createAgent } from '@agentuity/runtime';
import { z } from 'zod';
const agent = createAgent('TaskProcessor', {
schema: {
input: z.object({ task: z.string() }),
output: z.object({ result: z.string() })
},
handler: async (ctx, input) => {
ctx.logger.info('Processing task', { task: input.task });
// Simulate processing
await new Promise(resolve => setTimeout(resolve, 100));
return { result: `Completed: ${input.task}` };
}
});
// Agent lifecycle events
agent.addEventListener('started', (eventName, agent, ctx) => {
ctx.logger.info('Agent started', {
sessionId: ctx.sessionId
});
});
agent.addEventListener('completed', (eventName, agent, ctx) => {
ctx.logger.info('Agent completed successfully', {
sessionId: ctx.sessionId
});
});
agent.addEventListener('errored', (eventName, agent, ctx, error) => {
ctx.logger.error('Agent failed', {
sessionId: ctx.sessionId,
error: error.message
});
});
export default agent;App-level events can be added in app.ts:
import { createApp } from '@agentuity/runtime';
const app = createApp();
// Track all agent executions
app.addEventListener('agent.started', (eventName, agent, ctx) => {
app.logger.info('Agent execution started', {
session: ctx.sessionId
});
});
app.addEventListener('agent.completed', (eventName, agent, ctx) => {
app.logger.info('Agent execution completed', {
session: ctx.sessionId
});
});
export default app.server;Key Points:
- Agent-level events:
started,completed,errored - App-level events track all agents globally
- Session and thread events available for lifecycle tracking
- Events useful for monitoring, analytics, and debugging
Multi-Agent Workflow
Orchestrating complex workflows with conditional logic:
import { createAgent } from '@agentuity/runtime';
import { z } from 'zod';
import searchAgent from '@agent/search';
import deepAnalyzer from '@agent/deep-analyzer';
import basicAnalyzer from '@agent/basic-analyzer';
import recommendationEngine from '@agent/recommendation-engine';
const agent = createAgent('ResearchWorkflow', {
schema: {
input: z.object({
query: z.string(),
analysisDepth: z.enum(['basic', 'deep']),
includeRecommendations: z.boolean().optional()
}),
output: z.object({
searchResults: z.any(),
analysis: z.any(),
recommendations: z.array(z.string()).optional(),
summary: z.string()
})
},
handler: async (ctx, input) => {
ctx.logger.info(`Starting workflow: ${input.query}`);
// Step 1: Search for relevant information
const searchResults = await searchAgent.run({
query: input.query,
limit: 10
});
// Step 2: Analyze results (conditional based on depth)
const analysis = input.analysisDepth === 'deep'
? await deepAnalyzer.run({
data: searchResults,
includeDetails: true
})
: await basicAnalyzer.run({
data: searchResults
});
// Step 3: Generate recommendations (optional)
let recommendations;
if (input.includeRecommendations) {
const recResponse = await recommendationEngine.run({
analysis,
context: input.query
});
recommendations = recResponse.recommendations;
}
// Step 4: Create summary
const resultCount = searchResults.count || 0;
const summary = `Found ${resultCount} results, analyzed with ${input.analysisDepth} depth${
recommendations ? `, generated ${recommendations.length} recommendations` : ''
}`;
ctx.logger.info('Workflow completed successfully');
return {
searchResults,
analysis,
recommendations,
summary
};
}
});
export default agent;Key Points:
- Sequential agent orchestration
- Conditional workflows based on input
- Parallel execution where appropriate
- Data passing between agents
- Comprehensive logging for debugging
Background Tasks & Stream Creation
Using waitUntil for background processing and stream management:
import { createAgent } from '@agentuity/runtime';
import { z } from 'zod';
import { openai } from '@ai-sdk/openai';
import { streamText } from 'ai';
const agent = createAgent('BackgroundStreamer', {
schema: {
input: z.object({
action: z.enum(['stream-with-background', 'multi-step-stream']),
prompt: z.string().optional(),
userId: z.string().optional(),
includeAnalytics: z.boolean().optional()
}),
output: z.object({
streamId: z.string().optional(),
streamUrl: z.string().optional(),
progressStreamId: z.string().optional(),
progressStreamUrl: z.string().optional(),
status: z.string().optional(),
message: z.string().optional(),
estimatedDuration: z.string().optional()
})
},
handler: async (ctx, input) => {
const { action, prompt, userId, includeAnalytics = true } = input;
if (action === 'stream-with-background') {
// Create a stream for the LLM response
const stream = await ctx.stream.create('llm-response', {
contentType: 'text/plain',
metadata: {
userId: userId || ctx.sessionId,
model: 'gpt-5-nano',
type: 'llm-generation'
}
});
// Background task for streaming LLM response
ctx.waitUntil(async () => {
const { textStream } = streamText({
model: openai('gpt-5-nano'),
prompt: prompt || 'Tell me a short story'
});
await textStream.pipeTo(stream);
});
// Background task for analytics
if (includeAnalytics && userId) {
ctx.waitUntil(async () => {
await logRequestAnalytics(userId, {
action: 'stream_created',
streamId: stream.id,
timestamp: new Date()
});
});
}
// Background task for user activity tracking
if (userId) {
ctx.waitUntil(async () => {
await updateUserActivity(userId, 'llm_stream_request');
});
}
return {
streamId: stream.id,
streamUrl: stream.url,
status: 'streaming',
message: 'Stream created successfully'
};
}
if (action === 'multi-step-stream') {
const progressStream = await ctx.stream.create('progress', {
contentType: 'application/json',
metadata: {
type: 'multi-step-progress',
userId: userId || ctx.sessionId
}
});
ctx.waitUntil(async () => {
try {
const steps = [
{ name: 'Analyzing input', duration: 1000 },
{ name: 'Generating response', duration: 2000 },
{ name: 'Post-processing', duration: 500 },
{ name: 'Finalizing', duration: 300 }
];
for (let i = 0; i < steps.length; i++) {
const step = steps[i];
await progressStream.write(JSON.stringify({
step: i + 1,
total: steps.length,
name: step.name,
progress: ((i + 1) / steps.length) * 100,
timestamp: new Date().toISOString()
}) + '\n');
await new Promise(resolve => setTimeout(resolve, step.duration));
}
await progressStream.write(JSON.stringify({
completed: true,
message: 'All steps completed successfully',
timestamp: new Date().toISOString()
}) + '\n');
} finally {
await progressStream.close();
}
});
return {
progressStreamId: progressStream.id,
progressStreamUrl: progressStream.url,
estimatedDuration: '3-4 seconds'
};
}
return { message: 'Invalid action' };
}
});
// Helper functions
async function logRequestAnalytics(userId: string, data: any) {
console.log(`Analytics for user ${userId}:`, data);
}
async function updateUserActivity(userId: string, activity: string) {
console.log(`Updated activity for user ${userId}: ${activity}`);
}
export default agent;Key Points:
waitUntil()executes tasks after response is sent- Multiple background tasks can run concurrently
- Streams support metadata for organization
- Manual stream writing for progress updates
- Always close streams in finally blocks
Specialized Routes & Patterns
Email Route Handler
Handling incoming emails with automatic parsing:
import { createRouter } from '@agentuity/runtime';
import emailProcessor from '@agent/email-processor';
const router = createRouter();
router.email('support@example.com', async (email, c) => {
// Process email with agent
const result = await emailProcessor.run({
sender: email.fromEmail() || 'unknown',
subject: email.subject() || 'no subject',
content: email.text() || email.html() || ''
});
return c.json({
processed: true,
ticketId: result.ticketId
});
});
export default router;Key Points:
- Emails automatically parsed from RFC822 format
- Access to text, HTML, and attachments
- Route-level context provides agent access
WebSocket Real-Time Chat
Creating WebSocket endpoints for bidirectional communication:
import { createRouter } from '@agentuity/runtime';
import chatAgent from '@agent/chat';
const router = createRouter();
router.websocket('/chat', (c) => (ws) => {
ws.onOpen(() => {
ws.send(JSON.stringify({ type: 'connected' }));
});
ws.onMessage(async (event) => {
const message = JSON.parse(event.data);
// Process with agent
const response = await chatAgent.run({
message: message.text
});
ws.send(JSON.stringify({
type: 'response',
data: response
}));
});
ws.onClose(() => {
ctx.logger.info('Client disconnected');
});
});
export default router;Key Points:
- WebSocket lifecycle:
onOpen,onMessage,onClose - Bidirectional real-time communication
- Agent integration for message processing
Cron Scheduled Jobs
Scheduling recurring tasks with cron syntax:
import { createRouter } from '@agentuity/runtime';
import reportGenerator from '@agent/report-generator';
import notificationAgent from '@agent/notification';
import healthCheck from '@agent/health-check';
const router = createRouter();
// Daily report at 9 AM
router.cron('0 9 * * *', async (c) => {
ctx.logger.info('Running daily report generation');
const report = await reportGenerator.run({
type: 'daily',
date: new Date().toISOString(),
includeMetrics: true
});
// Store report
await ctx.kv.set('reports', `daily-${Date.now()}`, report);
// Send notification
await notificationAgent.run({
type: 'email',
subject: 'Daily Report Ready',
recipients: ['admin@example.com'],
reportId: report.id
});
return c.json({ success: true, reportId: report.id });
});
// Health check every 5 minutes
router.cron('*/5 * * * *', async (c) => {
const health = await healthCheck.run({});
if (health.status !== 'ok') {
ctx.logger.warn('Health check failed', { status: health.status });
}
return c.json({ healthy: health.status === 'ok' });
});
export default router;Cron schedule format:
┌───────────── minute (0-59)
│ ┌───────────── hour (0-23)
│ │ ┌───────────── day of month (1-31)
│ │ │ ┌───────────── month (1-12)
│ │ │ │ ┌───────────── day of week (0-6, Sunday=0)
│ │ │ │ │
* * * * *Key Points:
- Cron schedules defined in code, not UI
- Use standard cron syntax
- Access to full agent context
- Useful for reports, cleanup, monitoring
Server-Sent Events for Progress
Streaming progress updates to clients:
import { createRouter } from '@agentuity/runtime';
import processor from '@agent/processor';
const router = createRouter();
router.sse('/updates', (c) => async (stream) => {
// Send initial connection
await stream.write({ type: 'connected' });
// Process task and send updates
const updates = await processor.run({ task: 'process' });
for (const update of updates) {
await stream.write({
type: 'progress',
data: update
});
}
await stream.write({ type: 'complete' });
stream.onAbort(() => {
ctx.logger.info('Client disconnected');
});
});
export default router;Key Points:
- Server-to-client streaming only (one-way)
- Automatic reconnection on client side
onAbortcalled when client disconnects- Use for progress tracking, live updates
For complete routing documentation including HTTP methods, route parameters, query strings, validation with zValidator, and additional route types like Stream and SMS, see the Routing & Triggers Guide.
Advanced Patterns
Agent Evaluations
Automatically testing agent outputs for quality:
import { createAgent } from '@agentuity/runtime';
import { z } from 'zod';
const agent = createAgent('QueryProcessor', {
schema: {
input: z.object({ query: z.string() }),
output: z.object({
result: z.string(),
confidence: z.number()
})
},
handler: async (ctx, input) => {
// Process query and generate result
const result = await processQuery(input.query);
return {
result,
confidence: 0.85 // Confidence score based on processing
};
}
});
// Basic evaluation: confidence threshold check
agent.createEval('confidence-check', {
description: 'Ensures confidence score meets minimum threshold',
handler: async (ctx, input, output) => {
const passed = output.confidence >= 0.8;
return {
success: true,
passed,
metadata: {
confidence: output.confidence,
threshold: 0.8,
reason: passed ? 'Sufficient confidence' : 'Low confidence'
}
};
}
});
async function processQuery(query: string) {
// Your processing logic here
return `Processed: ${query}`;
}
export default agent;Key Points:
- Evals run automatically after agent completes
- Non-blocking execution via
waitUntil() - Access to input, output, and full context
- Pass/fail pattern with metadata
For comprehensive evaluation patterns including LLM-as-judge, hallucination detection, RAG quality metrics (contextual relevancy, answer relevancy, faithfulness), and A/B testing, see the Evaluations Guide.
Telemetry & Distributed Tracing
Using OpenTelemetry for observability:
import { createAgent } from '@agentuity/runtime';
import { SpanStatusCode } from '@opentelemetry/api';
const agent = createAgent('TracedProcessor', {
handler: async (ctx, input) => {
return ctx.tracer.startActiveSpan('process-request', async (span) => {
try {
// Add attributes to the span
span.setAttribute('data.type', typeof input);
// Create a child span for data processing
return await ctx.tracer.startActiveSpan('process-data', async (childSpan) => {
try {
// Add event to the span
childSpan.addEvent('processing-started', {
timestamp: Date.now()
});
// Simulate data processing
const result = await processData(input);
// Add event to the span
childSpan.addEvent('processing-completed', {
timestamp: Date.now(),
resultSize: JSON.stringify(result).length
});
// Set span status
childSpan.setStatus({ code: SpanStatusCode.OK });
return result;
} catch (error) {
// Record exception in the span
childSpan.recordException(error as Error);
childSpan.setStatus({
code: SpanStatusCode.ERROR,
message: (error as Error).message
});
throw error;
} finally {
childSpan.end();
}
});
} catch (error) {
// Record exception in the parent span
span.recordException(error as Error);
span.setStatus({
code: SpanStatusCode.ERROR,
message: (error as Error).message
});
ctx.logger.error('Error processing request', error);
throw error;
} finally {
span.end();
}
});
}
});
async function processData(data: any) {
await new Promise(resolve => setTimeout(resolve, 100));
return {
processed: true,
input: data,
timestamp: new Date().toISOString()
};
}
export default agent;Key Points:
- Hierarchical spans (parent/child relationships)
- Span attributes for filtering and analysis
- Event recording for granular tracking
- Exception recording for error analysis
- OpenTelemetry standard for distributed tracing
Streaming Examples
OpenAI Streaming
Streaming LLM responses with the Vercel AI SDK:
import { createAgent } from '@agentuity/runtime';
import { z } from 'zod';
import { streamText } from 'ai';
import { openai } from '@ai-sdk/openai';
const agent = createAgent('OpenAIStreamer', {
schema: {
input: z.object({
prompt: z.string()
}),
stream: true
},
handler: async (ctx, input) => {
const { textStream } = streamText({
model: openai('gpt-5-nano'),
prompt: input.prompt
});
return textStream;
}
});
export default agent;Key Points:
stream: truein schema enables streaming- Direct return of stream from Vercel AI SDK
- Responsive user experience with real-time output
Dependencies:
bun add ai @ai-sdk/openaiAgent-to-Agent Streaming
Calling another agent and streaming its response:
import { createAgent } from '@agentuity/runtime';
import { z } from 'zod';
import historyExpert from '@agent/history-expert';
const agent = createAgent('StreamingRouter', {
schema: {
input: z.object({
question: z.string()
}),
stream: true
},
handler: async (ctx, input) => {
// Call the expert agent and stream its response
const response = await historyExpert.run({
question: input.question
});
return response;
}
});
export default agent;Key Points:
- Stream responses pass through seamlessly
- Enables agent chaining with streaming
- Maintains end-to-end streaming experience
Learn More:
Need Help?
Join our Community for assistance or just to hang with other humans building agents.
Send us an email at hi@agentuity.com if you'd like to get in touch.
Please Follow us on
If you haven't already, please Signup for your free account now and start building your first agent!