From 915b99ca5f7fc684d0e0cab1e8200a16e18b3374 Mon Sep 17 00:00:00 2001 From: parteeksingh24 Date: Tue, 18 Nov 2025 09:20:49 -0800 Subject: [PATCH 01/63] Add v1 docs --- .gitignore | 5 + content/meta.json | 3 +- content/v1/Examples/index.mdx | 1412 +++++++++++ content/v1/Guides/agent-communication.mdx | 859 +++++++ content/v1/Guides/evaluations.mdx | 918 +++++++ content/v1/Guides/events.mdx | 685 ++++++ content/v1/Guides/key-value-storage.mdx | 277 +++ content/v1/Guides/routing-triggers.mdx | 476 ++++ content/v1/Guides/schema-validation.mdx | 711 ++++++ content/v1/Guides/sessions-threads.mdx | 880 +++++++ content/v1/Guides/subagents.mdx | 747 ++++++ content/v1/Guides/vector-storage.mdx | 954 ++++++++ content/v1/Introduction/architecture.mdx | 426 ++++ content/v1/Introduction/core-concepts.mdx | 308 +++ content/v1/Introduction/introduction.mdx | 38 + content/v1/SDK/api-reference.mdx | 2693 +++++++++++++++++++++ content/v1/SDK/core-concepts.mdx | 514 ++++ content/v1/SDK/error-handling.mdx | 626 +++++ content/v1/meta.json | 10 + content/v1/migration-guide.mdx | 1036 ++++++++ 20 files changed, 13577 insertions(+), 1 deletion(-) create mode 100644 content/v1/Examples/index.mdx create mode 100644 content/v1/Guides/agent-communication.mdx create mode 100644 content/v1/Guides/evaluations.mdx create mode 100644 content/v1/Guides/events.mdx create mode 100644 content/v1/Guides/key-value-storage.mdx create mode 100644 content/v1/Guides/routing-triggers.mdx create mode 100644 content/v1/Guides/schema-validation.mdx create mode 100644 content/v1/Guides/sessions-threads.mdx create mode 100644 content/v1/Guides/subagents.mdx create mode 100644 content/v1/Guides/vector-storage.mdx create mode 100644 content/v1/Introduction/architecture.mdx create mode 100644 content/v1/Introduction/core-concepts.mdx create mode 100644 content/v1/Introduction/introduction.mdx create mode 100644 content/v1/SDK/api-reference.mdx create mode 100644 content/v1/SDK/core-concepts.mdx create mode 100644 content/v1/SDK/error-handling.mdx create mode 100644 content/v1/meta.json create mode 100644 content/v1/migration-guide.mdx diff --git a/.gitignore b/.gitignore index a45f3bb2..91a8bebe 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,8 @@ next-env.d.ts # this is generated by the build process content/docs.json + +# Claude Code +.claude/ +claude/ +CLAUDE.md diff --git a/content/meta.json b/content/meta.json index 77852ca1..ef46f8f7 100644 --- a/content/meta.json +++ b/content/meta.json @@ -8,6 +8,7 @@ "SDKs", "Changelog", "Examples", - "Troubleshooting" + "Troubleshooting", + "v1" ] } diff --git a/content/v1/Examples/index.mdx b/content/v1/Examples/index.mdx new file mode 100644 index 00000000..c57d3904 --- /dev/null +++ b/content/v1/Examples/index.mdx @@ -0,0 +1,1412 @@ +--- +title: Examples +description: 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: + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const agent = createAgent({ + 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: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + 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](/Guides/schema-validation). + + +### Agent with Structured Logging + +This example demonstrates structured logging for monitoring and debugging: + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const agent = createAgent({ + 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 `sessionId` for request tracing + +## Core Features + +### Key-Value Storage + +This example demonstrates how to use the key-value storage API for data persistence: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import * as v from 'valibot'; + +const agent = createAgent({ + 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 with `exists` flag +- `ctx.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: + +```typescript +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({ + 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: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import * as v from 'valibot'; + +const agent = createAgent({ + 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') { + await ctx.objectstore.put('documents', filename, data, { + contentType: 'application/pdf', + metadata: { + uploadedBy: ctx.sessionId, + uploadedAt: new Date().toISOString() + } + }); + + return { + success: true, + message: 'File uploaded successfully' + }; + } + + if (action === 'download') { + const result = await ctx.objectstore.get('documents', filename); + + if (!result.exists) { + return { + success: false, + message: 'File not found' + }; + } + + return { + success: true, + data: result.data + }; + } + + if (action === 'share') { + const url = await ctx.objectstore.createPublicURL( + 'documents', + filename, + expiresIn || 3600000 // Default 1 hour + ); + + return { + success: true, + url, + message: `URL expires in ${expiresIn || 3600000}ms` + }; + } + + // Delete + const deleted = await ctx.objectstore.delete('documents', filename); + + return { + success: deleted, + message: deleted ? 'File deleted' : 'File not found' + }; + } +}); + +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: + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const agent = createAgent({ + handler: async (ctx, input) => { + ctx.logger.info('Starting multi-agent workflow'); + + // Call enrichment agent + const enriched = await ctx.agent.enrichmentAgent.run({ + text: input.text + }); + + // Call multiple agents in parallel + const [analyzed, categorized] = await Promise.all([ + ctx.agent.analyzerAgent.run({ data: enriched }), + ctx.agent.categorizerAgent.run({ data: enriched }) + ]); + + // Call final processing agent with combined results + const final = await ctx.agent.processorAgent.run({ + analyzed, + categorized, + original: input + }); + + return { + processed: true, + results: final + }; + } +}); + +export default agent; +``` + +**Key Points:** +- Access agents via `ctx.agent.agentName.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: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; +import { generateText } from 'ai'; +import { openai } from '@ai-sdk/openai'; + +const agent = createAgent({ + 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: + +```typescript +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({ + 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 await result.data.json(); +} + +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: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + 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: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + 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', { + agentName: agent.metadata.name, + sessionId: ctx.sessionId + }); +}); + +agent.addEventListener('completed', (eventName, agent, ctx) => { + ctx.logger.info('Agent completed successfully', { + agentName: agent.metadata.name, + sessionId: ctx.sessionId + }); +}); + +agent.addEventListener('errored', (eventName, agent, ctx, error) => { + ctx.logger.error('Agent failed', { + agentName: agent.metadata.name, + sessionId: ctx.sessionId, + error: error.message + }); +}); + +export default agent; +``` + +**App-level events** can be added in `app.ts`: + +```typescript +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', { + agent: agent.metadata.name, + session: ctx.sessionId + }); +}); + +app.addEventListener('agent.completed', (eventName, agent, ctx) => { + app.logger.info('Agent execution completed', { + agent: agent.metadata.name, + 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: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + 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 ctx.agent.searchAgent.run({ + query: input.query, + limit: 10 + }); + + // Step 2: Analyze results (conditional based on depth) + const analysis = input.analysisDepth === 'deep' + ? await ctx.agent.deepAnalyzer.run({ + data: searchResults, + includeDetails: true + }) + : await ctx.agent.basicAnalyzer.run({ + data: searchResults + }); + + // Step 3: Generate recommendations (optional) + let recommendations; + if (input.includeRecommendations) { + const recResponse = await ctx.agent.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: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; +import { openai } from '@ai-sdk/openai'; +import { streamText } from 'ai'; + +const agent = createAgent({ + 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: + +```typescript +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +router.email('support@example.com', async (email, c) => { + // Process email with agent + const result = await c.agent.emailProcessor.run({ + sender: email.from, + subject: email.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: + +```typescript +import { createRouter } from '@agentuity/runtime'; + +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 c.agent.chatAgent.run({ + message: message.text + }); + + ws.send(JSON.stringify({ + type: 'response', + data: response + })); + }); + + ws.onClose(() => { + c.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: + +```typescript +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +// Daily report at 9 AM +router.cron('0 9 * * *', async (c) => { + c.logger.info('Running daily report generation'); + + const report = await c.agent.reportGenerator.run({ + type: 'daily', + date: new Date().toISOString(), + includeMetrics: true + }); + + // Store report + await c.kv.set('reports', `daily-${Date.now()}`, report); + + // Send notification + await c.agent.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 c.agent.healthCheck.run({}); + + if (health.status !== 'ok') { + c.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: + +```typescript +import { createRouter } from '@agentuity/runtime'; + +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 c.agent.processor.run({ task: 'process' }); + + for (const update of updates) { + await stream.write({ + type: 'progress', + data: update + }); + } + + await stream.write({ type: 'complete' }); + + stream.onAbort(() => { + c.logger.info('Client disconnected'); + }); +}); + +export default router; +``` + +**Key Points:** +- Server-to-client streaming only (one-way) +- Automatic reconnection on client side +- `onAbort` called 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](/Guides/routing-triggers). + + +## Advanced Patterns + +### Agent Evaluations + +Automatically testing agent outputs for quality: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + 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({ + metadata: { + name: '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](/Guides/evaluations). + + +### Telemetry & Distributed Tracing + +Using OpenTelemetry for observability: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { SpanStatusCode } from '@opentelemetry/api'; + +const agent = createAgent({ + handler: async (ctx, input) => { + return ctx.tracer.startActiveSpan('process-request', async (span) => { + try { + // Add attributes to the span + span.setAttribute('data.type', typeof input); + span.setAttribute('agent.name', ctx.agentName || 'unknown'); + + // 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: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; +import { streamText } from 'ai'; +import { openai } from '@ai-sdk/openai'; + +const agent = createAgent({ + 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: true` in schema enables streaming +- Direct return of stream from Vercel AI SDK +- Responsive user experience with real-time output + +**Dependencies:** +```bash +npm install ai @ai-sdk/openai +``` + +### Agent-to-Agent Streaming + +Calling another agent and streaming its response: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ + question: z.string() + }), + stream: true + }, + handler: async (ctx, input) => { + // Call the expert agent and stream its response + const response = await ctx.agent.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:** +- [Agents just want to have streams](https://agentuity.com/blog/agent-streaming) +- TODO: add a streams demo for v1 diff --git a/content/v1/Guides/agent-communication.mdx b/content/v1/Guides/agent-communication.mdx new file mode 100644 index 00000000..c1c75033 --- /dev/null +++ b/content/v1/Guides/agent-communication.mdx @@ -0,0 +1,859 @@ +--- +title: Agent Communication +description: Learn how to build multi-agent systems with effective communication patterns +--- + +# Agent Communication + +TODO: no handoff, inter-project/org feature in v1? + +Build complex agentic systems by creating specialized agents that communicate and collaborate to achieve goals. + +## Overview + +In advanced agentic systems, agents work together by delegating tasks, sharing data, and coordinating workflows. The recommended approach is to build agents with highly specialized roles and use agent-to-agent communication to achieve the overall goal. + +For example, instead of building one large "customer support" agent, you might build: +- A **routing agent** that classifies the request +- A **knowledge agent** that searches documentation +- A **response agent** that generates customer-friendly answers +- A **sentiment agent** that analyzes customer tone + +Each agent focuses on one task and communicates with others as needed. + +## Basic Agent Calling + +Agents call other agents using the context object's agent registry. The SDK provides type-safe access to all agents in your project. + +### Calling an Agent + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const coordinatorAgent = createAgent({ + schema: { + input: z.object({ text: z.string() }), + output: z.object({ result: z.string() }) + }, + handler: async (ctx, input) => { + // Call another agent + const enriched = await ctx.agent.enrichmentAgent.run({ + text: input.text + }); + + // Use the result + return { result: enriched.enrichedText }; + } +}); +``` + +When both agents have schemas, the call is fully type-safe. TypeScript validates the input type and infers the output type automatically. + +For detailed information about the agent calling API, see [Core Concepts](/SDK/core-concepts#agent-communication). + +## Communication Patterns + +Different workflows require different communication patterns. This section covers the most common patterns for coordinating multiple agents. + +### Sequential Workflows + +Sequential execution processes data through a series of agents, where each agent depends on the output of the previous agent. + +```typescript +const pipelineAgent = createAgent({ + handler: async (ctx, input) => { + // Step 1: Validate input + const validated = await ctx.agent.validatorAgent.run({ + data: input.rawData + }); + + // Step 2: Enrich with additional data + const enriched = await ctx.agent.enrichmentAgent.run({ + data: validated.cleanData + }); + + // Step 3: Analyze the enriched data + const analyzed = await ctx.agent.analysisAgent.run({ + data: enriched.enrichedData + }); + + return analyzed; + } +}); +``` + +**When to use:** +- Data transformation pipelines +- Multi-step validation or processing +- Workflows where each step depends on the previous result + +**Error handling:** +Errors propagate automatically. If `validatorAgent` throws an error, `enrichmentAgent` and `analysisAgent` never execute. + +### Parallel Execution + +Parallel execution runs multiple agents simultaneously when their operations are independent. + +```typescript +const searchAgent = createAgent({ + handler: async (ctx, input) => { + // Execute all searches in parallel + const [webResults, dbResults, vectorResults] = await Promise.all([ + ctx.agent.webSearchAgent.run({ query: input.query }), + ctx.agent.databaseAgent.run({ query: input.query }), + ctx.agent.vectorSearchAgent.run({ query: input.query }) + ]); + + // Merge and rank results + return { + results: mergeResults(webResults, dbResults, vectorResults) + }; + } +}); +``` + +**When to use:** +- Independent data fetching operations +- Multiple validation checks +- Gathering data from different sources +- Operations that can run concurrently + +**Performance benefit:** +If each agent takes 1 second, sequential execution takes 3 seconds, but parallel execution takes only 1 second. + +### Conditional Routing + +Use an LLM to classify user intent and route to the appropriate agent: + +```typescript +import { groq } from '@ai-sdk/groq'; +import { generateObject } from 'ai'; +import { z } from 'zod'; + +const IntentSchema = z.object({ + agentType: z.enum(['support', 'sales', 'technical', 'billing']), + confidence: z.number().min(0).max(1), + reasoning: z.string() +}); + +const routerAgent = createAgent({ + schema: { + input: z.object({ userMessage: z.string() }) + }, + handler: async (ctx, input) => { + // Classify intent with Groq (fast inference via AI Gateway) + const intent = await generateObject({ + model: groq('llama-3.3-70b'), // Groq (fast inference via AI Gateway) for quick classification + schema: IntentSchema, + system: 'You are a request classifier. Analyze the user message and determine which agent should handle it. Return the agent type, confidence score, and brief reasoning.', + prompt: input.userMessage, + temperature: 0.0 // Deterministic output for routing decisions + }); + + ctx.logger.info('Intent classified', { + type: intent.object.agentType, + confidence: intent.object.confidence + }); + + // Route based on classified intent + switch (intent.object.agentType) { + case 'support': + return await ctx.agent.supportAgent.run({ + message: input.userMessage, + context: intent.object.reasoning + }); + + case 'sales': + return await ctx.agent.salesAgent.run({ + message: input.userMessage, + context: intent.object.reasoning + }); + + case 'technical': + return await ctx.agent.technicalAgent.run({ + message: input.userMessage, + context: intent.object.reasoning + }); + + case 'billing': + return await ctx.agent.billingAgent.run({ + message: input.userMessage, + context: intent.object.reasoning + }); + } + } +}); +``` + +**When to use:** +- Natural language user inputs +- Multiple potential routing paths +- Need to understand nuanced intent +- Adaptive routing based on context + +## Advanced Workflows + +### Multi-Stage Pipelines + +Build complex pipelines with checkpoints and conditional branching: + +```typescript +const contentModerationAgent = createAgent({ + handler: async (ctx, input) => { + // Stage 1: Initial screening + const screening = await ctx.agent.screeningAgent.run({ + content: input.text + }); + + if (!screening.passed) { + return { + approved: false, + reason: 'Failed initial screening', + flags: screening.flags + }; + } + + // Stage 2: Detailed analysis (parallel) + const [sentiment, toxicity, pii] = await Promise.all([ + ctx.agent.sentimentAgent.run({ text: input.text }), + ctx.agent.toxicityAgent.run({ text: input.text }), + ctx.agent.piiDetectionAgent.run({ text: input.text }) + ]); + + // Stage 3: Final decision + const decision = await ctx.agent.decisionAgent.run({ + screening, + sentiment, + toxicity, + pii + }); + + return decision; + } +}); +``` + +This pattern combines sequential and parallel execution with conditional logic for sophisticated workflows. + +### Fan-Out/Fan-In + +Distribute work to multiple agents and aggregate the results: + +```typescript +const documentAnalysisAgent = createAgent({ + schema: { + input: z.object({ + document: z.string(), + sections: z.array(z.string()) + }) + }, + handler: async (ctx, input) => { + // Fan-out: Analyze each section in parallel + const sectionAnalyses = await Promise.all( + input.sections.map(section => + ctx.agent.sectionAnalyzer.run({ text: section }) + ) + ); + + // Fan-in: Aggregate results + const summary = await ctx.agent.summaryAgent.run({ + analyses: sectionAnalyses + }); + + return { + sections: sectionAnalyses, + summary: summary.overall + }; + } +}); +``` + +**Handling partial failures:** + +```typescript +const results = await Promise.allSettled( + input.items.map(item => + ctx.agent.processingAgent.run({ item }) + ) +); + +const successful = results + .filter(r => r.status === 'fulfilled') + .map(r => r.value); + +const failed = results + .filter(r => r.status === 'rejected') + .map(r => r.reason); + +if (failed.length > 0) { + ctx.logger.warn('Some operations failed', { failed }); +} + +return { successful, failed: failed.length }; +``` + +## Error Handling Strategies + +### Cascading Failures + +By default, errors propagate through the call chain, stopping execution immediately: + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + // If validatorAgent throws, execution stops here + const validated = await ctx.agent.validatorAgent.run(input); + + // This line never executes if validation fails + const processed = await ctx.agent.processorAgent.run(validated); + + return processed; + } +}); +``` + +This is the recommended pattern for critical operations where later steps cannot proceed without earlier results. + +### Graceful Degradation + +For optional operations, catch errors and continue with reduced functionality: + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + let enrichedData = input.data; + + // Try to enrich, but continue if it fails + try { + const enrichment = await ctx.agent.enrichmentAgent.run({ + data: input.data + }); + enrichedData = enrichment.data; + } catch (error) { + ctx.logger.warn('Enrichment failed, using original data', { + error: error instanceof Error ? error.message : String(error) + }); + } + + // Process with enriched data (or original if enrichment failed) + return await ctx.agent.processorAgent.run({ + data: enrichedData + }); + } +}); +``` + +**When to use graceful degradation:** +- Optional external API calls +- Caching operations (fall back to database) +- Non-critical enrichment services +- Analytics or logging operations + +### Retry Strategies + +Implement retry logic for unreliable operations: + +```typescript +async function callWithRetry( + fn: () => Promise, + maxRetries: number = 3, + delayMs: number = 1000 +): Promise { + for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + return await fn(); + } catch (error) { + if (attempt === maxRetries) { + throw error; + } + + // Exponential backoff + const delay = delayMs * Math.pow(2, attempt - 1); + await new Promise(resolve => setTimeout(resolve, delay)); + } + } + throw new Error('Retry failed'); +} + +const agent = createAgent({ + handler: async (ctx, input) => { + // Retry unreliable external agent call + const result = await callWithRetry(() => + ctx.agent.externalServiceAgent.run(input) + ); + + return result; + } +}); +``` + +### Fallback Strategies + +Provide alternative paths when primary agents fail: + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + try { + // Try primary agent + return await ctx.agent.primaryAgent.run(input); + } catch (error) { + ctx.logger.warn('Primary agent failed, using fallback', { error }); + + // Fall back to alternative agent + return await ctx.agent.fallbackAgent.run(input); + } + } +}); +``` + +## Data Flow & Transformation + +### Output to Input Mapping + +Transform agent outputs to match the expected input of downstream agents: + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + // Agent 1 returns { analysisResult: {...} } + const analysis = await ctx.agent.analysisAgent.run({ + text: input.text + }); + + // Agent 2 expects { data: {...}, metadata: {...} } + const processed = await ctx.agent.processingAgent.run({ + data: analysis.analysisResult, + metadata: { + timestamp: new Date().toISOString(), + source: 'analysis-agent' + } + }); + + return processed; + } +}); +``` + +### Schema Compatibility + +Ensure type safety across agent boundaries using schemas: + +```typescript +// enrichmentAgent output schema +const enrichmentOutputSchema = z.object({ + enrichedText: z.string(), + metadata: z.object({ + sentiment: z.number(), + keywords: z.array(z.string()) + }) +}); + +// analysisAgent input schema (compatible with enrichment output) +const analysisInputSchema = z.object({ + enrichedText: z.string(), + metadata: z.object({ + sentiment: z.number(), + keywords: z.array(z.string()) + }) +}); +``` + +When schemas are compatible, TypeScript validates the connection at compile time. + +### Transformation Pipelines + +Build pipelines that progressively transform data: + +```typescript +const transformationAgent = createAgent({ + handler: async (ctx, input) => { + // Raw text → Structured data + const structured = await ctx.agent.parserAgent.run({ + text: input.rawText + }); + + // Structured data → Validated data + const validated = await ctx.agent.validatorAgent.run({ + data: structured.parsed + }); + + // Validated data → Enriched data + const enriched = await ctx.agent.enrichmentAgent.run({ + data: validated.clean + }); + + // Enriched data → Final output + const final = await ctx.agent.formatterAgent.run({ + data: enriched.enriched + }); + + return final; + } +}); +``` + +## Real-World Examples + +### Search + Summarize + Analyze Workflow + +```typescript +const researchAgent = createAgent({ + schema: { + input: z.object({ query: z.string() }), + output: z.object({ + summary: z.string(), + insights: z.array(z.string()), + sources: z.array(z.string()) + }) + }, + handler: async (ctx, input) => { + // Step 1: Search for relevant information + const searchResults = await ctx.agent.searchAgent.run({ + query: input.query, + limit: 10 + }); + + // Step 2: Summarize findings (parallel) + const summaries = await Promise.all( + searchResults.results.map(result => + ctx.agent.summaryAgent.run({ text: result.content }) + ) + ); + + // Step 3: Analyze and extract insights + const analysis = await ctx.agent.analysisAgent.run({ + summaries: summaries.map(s => s.summary), + originalQuery: input.query + }); + + return { + summary: analysis.overallSummary, + insights: analysis.keyInsights, + sources: searchResults.results.map(r => r.url) + }; + } +}); +``` + +### Multi-Step Approval System + +```typescript +const approvalAgent = createAgent({ + schema: { + input: z.object({ + request: z.any(), + requester: z.string() + }) + }, + handler: async (ctx, input) => { + // Step 1: Validate request format + const validation = await ctx.agent.validatorAgent.run({ + request: input.request + }); + + if (!validation.valid) { + return { + approved: false, + reason: 'Invalid request format', + errors: validation.errors + }; + } + + // Step 2: Check user permissions + const permissions = await ctx.agent.permissionsAgent.run({ + userId: input.requester, + action: input.request.action + }); + + if (!permissions.allowed) { + return { + approved: false, + reason: 'Insufficient permissions' + }; + } + + // Step 3: Risk assessment (parallel checks) + const [financialRisk, securityRisk, complianceCheck] = await Promise.all([ + ctx.agent.financialRiskAgent.run(input.request), + ctx.agent.securityRiskAgent.run(input.request), + ctx.agent.complianceAgent.run(input.request) + ]); + + // Step 4: Final approval decision + const decision = await ctx.agent.decisionAgent.run({ + request: input.request, + permissions, + financialRisk, + securityRisk, + complianceCheck + }); + + return decision; + } +}); +``` + +### Data Enrichment Pipeline + +```typescript +const dataEnrichmentAgent = createAgent({ + handler: async (ctx, input) => { + // Start with raw user data + let userData = input.userData; + + // Parallel enrichment from multiple sources + const [ + demographicData, + behavioralData, + preferenceData + ] = await Promise.all([ + ctx.agent.demographicAgent.run({ userId: userData.id }), + ctx.agent.behavioralAgent.run({ userId: userData.id }), + ctx.agent.preferenceAgent.run({ userId: userData.id }) + ]); + + // Merge all data + const merged = { + ...userData, + demographics: demographicData, + behavior: behavioralData, + preferences: preferenceData + }; + + // Generate insights from enriched data + const insights = await ctx.agent.insightsAgent.run({ + enrichedData: merged + }); + + // Store for future use + try { + await ctx.kv.set('enriched-users', userData.id, { + data: merged, + insights: insights, + updatedAt: new Date().toISOString() + }, { ttl: 86400 }); // 24 hours + } catch (error) { + ctx.logger.warn('Failed to cache enriched data', { error }); + } + + return { + enrichedData: merged, + insights: insights + }; + } +}); +``` + +## Subagent Communication + +For parent-child agent hierarchies, the SDK provides specialized patterns. Subagents are useful when agents share a common domain or need coordinated access control. + +### Quick Example + +```typescript +// Call a subagent from anywhere +const result = await ctx.agent.team.members.run({ + action: 'list' +}); + +// From a subagent, access the parent +if (ctx.parent) { + const parentResult = await ctx.parent.run({ + action: 'validate' + }); +} +``` + +For comprehensive coverage of subagent patterns, validation strategies, and best practices, see the [Subagents](/Guides/subagents) guide. + +## Best Practices + +### Keep Agents Focused + +Each agent should have a single, well-defined responsibility: + +```typescript +// Good - focused agents +const validatorAgent = createAgent({ /* validates data */ }); +const enrichmentAgent = createAgent({ /* enriches data */ }); +const analysisAgent = createAgent({ /* analyzes data */ }); + +// Bad - monolithic agent doing everything +const megaAgent = createAgent({ + handler: async (ctx, input) => { + // Validates, enriches, analyzes all in one place + } +}); +``` + +Focused agents are easier to test, reuse, and maintain. + +### Use Schemas for Type Safety + +Define schemas for all agents to enable type-safe communication: + +```typescript +const sourceAgent = createAgent({ + schema: { + output: z.object({ + data: z.string(), + metadata: z.object({ timestamp: z.string() }) + }) + }, + handler: async (ctx, input) => { + return { + data: 'result', + metadata: { timestamp: new Date().toISOString() } + }; + } +}); + +const consumerAgent = createAgent({ + schema: { + input: z.object({ + data: z.string(), + metadata: z.object({ timestamp: z.string() }) + }) + }, + handler: async (ctx, input) => { + // TypeScript knows the exact shape of input + const result = await ctx.agent.sourceAgent.run({}); + + // This is type-safe - TypeScript validates compatibility + return await processData(result); + } +}); +``` + +### Handle Errors Appropriately + +Choose the right error handling strategy for each operation: + +**Fail-fast** for critical operations: +```typescript +// No try-catch - let errors propagate +const validated = await ctx.agent.validatorAgent.run(input); +``` + +**Graceful degradation** for optional operations: +```typescript +try { + await ctx.agent.optionalAgent.run(input); +} catch (error) { + ctx.logger.warn('Optional operation failed', { error }); +} +``` + +### Monitor Performance + +Use logging and tracing to monitor multi-agent workflows: + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + const startTime = Date.now(); + + ctx.logger.info('Starting multi-agent workflow', { + sessionId: ctx.sessionId + }); + + const result = await ctx.agent.processingAgent.run(input); + + ctx.logger.info('Workflow completed', { + duration: Date.now() - startTime, + sessionId: ctx.sessionId + }); + + return result; + } +}); +``` + +### Leverage Shared Context + +Agent calls execute within the same session context, sharing state: + +```typescript +const coordinatorAgent = createAgent({ + handler: async (ctx, input) => { + // Store data in thread state + ctx.thread.state.set('startTime', Date.now()); + ctx.thread.state.set('userId', input.userId); + + // Called agents can access the same thread state + const result = await ctx.agent.processingAgent.run(input); + + // All agents share the same sessionId + ctx.logger.info('Session ID:', ctx.sessionId); + + return result; + } +}); +``` + +Use this for: +- Tracking context across agent calls +- Sharing authentication/authorization data +- Maintaining conversation state +- Coordinating distributed workflows + +### Fallback and Error Handling for Classification + +When using LLM-based routing, handle classification failures gracefully: + +```typescript +const routerAgent = createAgent({ + handler: async (ctx, input) => { + try { + const intent = await generateObject({ + model: groq('llama-3.3-70b'), + schema: IntentSchema, + prompt: input.userMessage, + temperature: 0.0 + }); + + // Route based on intent + switch (intent.object.agentType) { + case 'support': + return await ctx.agent.supportAgent.run({ + message: input.userMessage, + context: intent.object.reasoning + }); + + case 'sales': + return await ctx.agent.salesAgent.run({ + message: input.userMessage, + context: intent.object.reasoning + }); + + case 'technical': + return await ctx.agent.technicalAgent.run({ + message: input.userMessage, + context: intent.object.reasoning + }); + } + } catch (error) { + ctx.logger.error('Intent classification failed', { + error: error instanceof Error ? error.message : String(error) + }); + + // Fallback to default agent + return await ctx.agent.defaultAgent.run({ + message: input.userMessage + }); + } + } +}); +``` + +This ensures your system remains functional even when classification fails. + +## Next Steps + +- [Subagents](/Guides/subagents) - Parent-child agent hierarchies and coordination patterns +- [Schema Validation](/Guides/schema-validation) - Type-safe schemas for agent inputs and outputs +- [Events](/Guides/events) - Monitor agent lifecycle and execution +- [Error Handling](/SDK/error-handling) - Comprehensive error handling strategies +- [Core Concepts](/SDK/core-concepts) - Detailed agent communication API reference diff --git a/content/v1/Guides/evaluations.mdx b/content/v1/Guides/evaluations.mdx new file mode 100644 index 00000000..b61ff6e5 --- /dev/null +++ b/content/v1/Guides/evaluations.mdx @@ -0,0 +1,918 @@ +--- +title: Evaluations +description: Automatically test and validate agent outputs for quality, accuracy, and compliance +--- + +The Agentuity SDK includes a built-in evaluation framework for automatically testing agent outputs. Evaluations run after each agent execution to validate quality, check compliance, monitor performance, and ensure outputs meet your requirements. + +Evals execute asynchronously without blocking agent responses, making them ideal for quality assurance in production environments. + +## What are Evaluations? + +Evaluations (evals) are automated tests that run after your agent completes. They receive the agent's input and output, then return a pass/fail result or quality score. + +**Key characteristics:** + +- **Automatic execution**: Evals run automatically after agent completion +- **Non-blocking**: Execute asynchronously using `waitUntil()`, so they don't delay responses +- **Type-safe**: Handler signatures automatically match your agent's schemas +- **Full context**: Access to logging, storage, tracing, and all agent services +- **Independent**: Multiple evals can run on a single agent; errors in one don't affect others + +**Use cases:** +- Quality checking (accuracy, relevance, completeness) +- Compliance validation (PII detection, content policy) +- Performance monitoring (response time, token usage) +- A/B testing and regression testing + +## Creating Evaluations + +Evaluations are created using the `createEval()` method on an agent instance. + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ question: z.string() }), + output: z.object({ answer: z.string(), confidence: z.number() }), + }, + handler: async (ctx, input) => { + const answer = await generateAnswer(input.question); + return { answer, confidence: 0.95 }; + }, +}); + +// Add an evaluation +agent.createEval({ + metadata: { + name: '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 ? 'Confidence acceptable' : 'Confidence too low' + } + }; + }, +}); +``` + +**Configuration:** + +```typescript +agent.createEval({ + metadata: { + name: string; // Required: eval name + description?: string; // Optional: what this eval checks + }, + handler: EvalFunction; // Required: eval logic +}); +``` + + +**Automatic Metadata**: The SDK automatically generates internal metadata (id, version, identifier, filename) for tracking and versioning. + + +## Eval Handler Signatures + +The eval handler signature automatically adapts based on your agent's schema configuration: + +| Agent Schema | Handler Signature | Use Case | +|--------------|------------------|----------| +| Input + Output | `(ctx, input, output) => EvalRunResult` | Most common - validate both input and output | +| Input only | `(ctx, input) => EvalRunResult` | Validate input format/content | +| Output only | `(ctx, output) => EvalRunResult` | Validate output quality | +| No schema | `(ctx) => EvalRunResult` | Check execution state/metrics | + +**Example: Input and Output Schemas** + +```typescript +const agent = createAgent({ + schema: { + input: z.object({ query: z.string() }), + output: z.object({ result: z.string() }), + }, + handler: async (ctx, input) => { + return { result: `Processed: ${input.query}` }; + }, +}); + +agent.createEval({ + metadata: { name: 'query-in-result' }, + handler: async (ctx, input, output) => { + // Handler receives both input and output + const queryInResult = output.result.includes(input.query); + + return { + success: true, + passed: queryInResult, + metadata: { + reason: queryInResult ? 'Query found in result' : 'Query missing from result' + } + }; + }, +}); +``` + +**Example: Input Schema Only** + +```typescript +const agent = createAgent({ + schema: { + input: z.object({ message: z.string() }), + }, + handler: async (ctx, input) => { + return `Received: ${input.message}`; + }, +}); + +agent.createEval({ + metadata: { name: 'input-validation' }, + handler: async (ctx, input) => { + // Handler receives only input + const isValid = input.message.length > 0 && input.message.length <= 500; + + return { + success: true, + passed: isValid, + metadata: { length: input.message.length, maxLength: 500 } + }; + }, +}); +``` + + +**Type Safety**: TypeScript automatically infers the correct handler signature based on your agent's schemas. Attempting to access unavailable parameters results in compile-time errors. + + +## Eval Results + +Evals can return three types of results: + +### Binary Pass/Fail + +```typescript +{ + success: true, + passed: boolean, + metadata?: Record +} +``` + +Use for clear yes/no validation (compliance checks, format verification). + +```typescript +agent.createEval({ + metadata: { name: 'length-check' }, + handler: async (ctx, input, output) => { + const passed = output.answer.length >= 50; + return { + success: true, + passed, + metadata: { + actualLength: output.answer.length, + minimumLength: 50 + } + }; + }, +}); +``` + +### Score-Based + +```typescript +{ + success: true, + score: number, // 0-1 range + metadata?: Record +} +``` + +Use for quality measurement and A/B testing. + +```typescript +agent.createEval({ + metadata: { name: 'quality-score' }, + handler: async (ctx, input, output) => { + let score = 0; + if (output.answer.length >= 100) score += 0.4; + if (output.answer.includes(input.question)) score += 0.3; + if (output.answer.includes('conclusion')) score += 0.3; + + return { success: true, score, metadata: { breakdown: 'length+relevance+completeness' } }; + }, +}); +``` + + +**Score Range**: Scores should be between 0 and 1, where 0 is worst and 1 is best. + + +### Error Results + +```typescript +{ + success: false, + error: string +} +``` + +Use when the eval itself fails to execute. + +```typescript +agent.createEval({ + metadata: { name: 'external-validation' }, + handler: async (ctx, input, output) => { + try { + const response = await fetch('https://api.example.com/validate', { + method: 'POST', + body: JSON.stringify({ text: output.answer }) + }); + + if (!response.ok) { + return { success: false, error: `Service returned ${response.status}` }; + } + + const result = await response.json(); + return { success: true, passed: result.valid }; + } catch (error) { + return { success: false, error: error.message }; + } + }, +}); +``` + +## Accessing Context + +Eval handlers receive the same context (`EvalContext`) as agent handlers: + +**Available Properties:** +- **Identifiers**: `ctx.sessionId`, `ctx.agentName` +- **Storage**: `ctx.kv`, `ctx.vector`, `ctx.objectstore`, `ctx.stream` +- **Observability**: `ctx.logger`, `ctx.tracer` +- **State**: `ctx.session`, `ctx.thread`, `ctx.state` +- **Agents**: `ctx.agent`, `ctx.current`, `ctx.parent` +- **Utilities**: `ctx.waitUntil()` + +**Example Using Multiple Context Features:** + +```typescript +agent.createEval({ + metadata: { name: 'comprehensive-tracker' }, + handler: async (ctx, input, output) => { + const startTime = ctx.state.get('startTime') as number; + const duration = Date.now() - startTime; + + // Store metrics + await ctx.kv.set('performance', ctx.sessionId, { + duration, + timestamp: Date.now(), + agentName: ctx.agentName + }); + + // Log with tracer + ctx.tracer.startActiveSpan('eval-tracking', (span) => { + span.setAttribute('duration_ms', duration); + span.end(); + }); + + // Log result + ctx.logger.info('Performance tracked', { sessionId: ctx.sessionId, duration }); + + return { + success: true, + passed: duration < 5000, + metadata: { durationMs: duration } + }; + }, +}); +``` + +## Multiple Evaluations + +Agents can have multiple evaluations that all run independently after each execution. + +```typescript +const agent = createAgent({ + schema: { + input: z.object({ text: z.string() }), + output: z.object({ + summary: z.string(), + keywords: z.array(z.string()) + }), + }, + handler: async (ctx, input) => { + return { + summary: generateSummary(input.text), + keywords: extractKeywords(input.text) + }; + }, +}); + +// Eval 1: Summary length +agent.createEval({ + metadata: { name: 'summary-length' }, + handler: async (ctx, input, output) => { + const passed = output.summary.length >= 20 && output.summary.length <= 200; + return { + success: true, + passed, + metadata: { length: output.summary.length, min: 20, max: 200 } + }; + }, +}); + +// Eval 2: Quality score +agent.createEval({ + metadata: { name: 'overall-quality' }, + handler: async (ctx, input, output) => { + let score = 0; + if (output.summary.length >= 50) score += 0.5; + if (output.keywords.length >= 3) score += 0.5; + + return { success: true, score }; + }, +}); +``` + +**File Organization:** + +For agents with multiple evals, use a separate `eval.ts` file: + +``` +agents/ + my-agent/ + agent.ts # Agent definition + eval.ts # All evals for this agent + route.ts # Route definitions +``` + + +**Independent Execution**: Errors in one eval don't prevent others from running. The SDK catches and logs all eval errors. + + +## Common Patterns + +### Task Completion Assessment + +Determine if the agent actually fulfilled the user's objective, not just whether it ran without errors. Uses LLM-as-judge to evaluate end-to-end success: + +```typescript +import { generateObject } from 'ai'; +import { openai } from '@ai-sdk/openai'; +import { z } from 'zod'; + +const customerServiceAgent = createAgent({ + schema: { + input: z.object({ + customerQuery: z.string(), + context: z.object({ + orderId: z.string().optional(), + customerId: z.string() + }) + }), + output: z.object({ + response: z.string(), + actionsTaken: z.array(z.string()) + }) + }, + handler: async (ctx, input) => { + // Agent processes customer request + return { + response: "Your order has been cancelled and refund processed.", + actionsTaken: ["cancelled_order", "issued_refund"] + }; + } +}); + +customerServiceAgent.createEval({ + metadata: { + name: 'task-completion-check', + description: 'Evaluates if agent successfully completed customer request' + }, + handler: async (ctx, input, output) => { + const { object } = await generateObject({ + model: openai('gpt-5-nano'), + schema: z.object({ + completed: z.boolean(), + reasoning: z.string(), + missingSteps: z.array(z.string()) + }), + prompt: `Evaluate if the agent successfully completed the customer's request. + +Customer Query: ${input.customerQuery} +Agent Response: ${output.response} +Actions Taken: ${output.actionsTaken.join(', ')} + +Did the agent fully address the customer's needs? Consider: +- Was the request understood correctly? +- Were appropriate actions taken? +- Was the response clear and helpful? +- Are there missing steps or incomplete resolution?` + }); + + return { + success: true, + passed: object.completed, + metadata: { + reasoning: object.reasoning, + missingSteps: object.missingSteps + } + }; + } +}); +``` + +### Hallucination Detection (Reference-Based) + +Verify that agent output is grounded in retrieved context, detecting unsupported claims. This pattern stores retrieved documents in `ctx.state` during handler execution for eval access: + +```typescript +import { generateObject } from 'ai'; +import { openai } from '@ai-sdk/openai'; +import { z } from 'zod'; + +const ragAgent = createAgent({ + schema: { + input: z.object({ question: z.string() }), + output: z.object({ + answer: z.string(), + sources: z.array(z.string()) + }) + }, + handler: async (ctx, input) => { + // Retrieve relevant documents + const results = await ctx.vector.search('knowledge-base', { + query: input.question, + limit: 3 + }); + + // Store retrieved context for eval access + ctx.state.set('retrievedDocs', results.map(r => r.metadata?.text || '')); + + // Generate answer using LLM + context + const answer = await generateAnswer(input.question, results); + + return { + answer, + sources: results.map(r => r.id) + }; + } +}); + +ragAgent.createEval({ + metadata: { + name: 'hallucination-check', + description: 'Detects claims not supported by retrieved sources' + }, + handler: async (ctx, input, output) => { + // Access retrieved documents from handler execution + const retrievedDocs = ctx.state.get('retrievedDocs') as string[]; + + const { object } = await generateObject({ + model: openai('gpt-5-nano'), + schema: z.object({ + isGrounded: z.boolean(), + unsupportedClaims: z.array(z.string()), + score: z.number().min(0).max(1) + }), + prompt: `Check if this answer is fully supported by the source documents. + +Question: ${input.question} +Answer: ${output.answer} + +Source Documents: +${retrievedDocs.join('\n\n')} + +Identify any claims in the answer that are NOT supported by the sources.` + }); + + return { + success: true, + score: object.score, + metadata: { + isGrounded: object.isGrounded, + unsupportedClaims: object.unsupportedClaims + } + }; + } +}); +``` + + +**State Persistence**: Data stored in `ctx.state` during agent execution persists through to eval handlers, enabling patterns like passing retrieved documents to hallucination checks. + + +### Compliance Validation + +Check for policy violations or sensitive content: + +```typescript +agent.createEval({ + metadata: { name: 'content-safety' }, + handler: async (ctx, input, output) => { + const profanityList = ['badword1', 'badword2']; + const piiPatterns = { + email: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/, + phone: /\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/, + ssn: /\b\d{3}-\d{2}-\d{4}\b/ + }; + + const violations = []; + const lowerOutput = output.answer.toLowerCase(); + + // Check profanity + for (const word of profanityList) { + if (lowerOutput.includes(word)) { + violations.push(`Profanity: ${word}`); + } + } + + // Check PII + for (const [type, pattern] of Object.entries(piiPatterns)) { + if (pattern.test(output.answer)) { + violations.push(`PII: ${type}`); + } + } + + return { + success: true, + passed: violations.length === 0, + metadata: { violations, violationCount: violations.length } + }; + }, +}); +``` + +### RAG Quality Metrics + +For retrieval-augmented generation systems, evaluate three distinct quality dimensions with focused, independent evals: + +```typescript +import { generateObject } from 'ai'; +import { openai } from '@ai-sdk/openai'; +import { z } from 'zod'; + +const ragAgent = createAgent({ + schema: { + input: z.object({ query: z.string() }), + output: z.object({ + answer: z.string(), + confidence: z.number() + }) + }, + handler: async (ctx, input) => { + const results = await ctx.vector.search('docs', { + query: input.query, + limit: 5, + similarity: 0.7 + }); + + // Store for eval access + ctx.state.set('retrievedResults', results); + + const answer = await generateAnswer(input.query, results); + + return { answer, confidence: 0.85 }; + } +}); + +// Eval 1: Contextual Relevancy - were the right docs retrieved? +ragAgent.createEval({ + metadata: { + name: 'contextual-relevancy', + description: 'Evaluates if retrieved documents are relevant to query' + }, + handler: async (ctx, input, output) => { + const results = ctx.state.get('retrievedResults') as VectorSearchResult[]; + + const { object } = await generateObject({ + model: openai('gpt-5-nano'), + schema: z.object({ + score: z.number().min(0).max(1), + irrelevantDocs: z.array(z.number()) + }), + prompt: `Rate how relevant these retrieved documents are to the query. + +Query: ${input.query} + +Documents: +${results.map((r, i) => `${i + 1}. ${r.metadata?.text}`).join('\n')} + +Score 0-1 for overall relevance. List indices of irrelevant docs.` + }); + + return { + success: true, + score: object.score, + metadata: { irrelevantDocs: object.irrelevantDocs } + }; + } +}); + +// Eval 2: Answer Relevancy - does the answer address the question? +ragAgent.createEval({ + metadata: { + name: 'answer-relevancy', + description: 'Evaluates if answer addresses the question' + }, + handler: async (ctx, input, output) => { + const { object } = await generateObject({ + model: openai('gpt-5-nano'), + schema: z.object({ + score: z.number().min(0).max(1), + reasoning: z.string() + }), + prompt: `Rate how well this answer addresses the question. + +Question: ${input.query} +Answer: ${output.answer} + +Score 0-1 for relevancy. Explain your reasoning.` + }); + + return { + success: true, + score: object.score, + metadata: { reasoning: object.reasoning } + }; + } +}); + +// Eval 3: Faithfulness - is answer faithful to sources? +ragAgent.createEval({ + metadata: { + name: 'faithfulness', + description: 'Checks if answer contains information not in sources' + }, + handler: async (ctx, input, output) => { + const results = ctx.state.get('retrievedResults') as VectorSearchResult[]; + const sources = results.map(r => r.metadata?.text).join('\n\n'); + + const { object } = await generateObject({ + model: openai('gpt-5-nano'), + schema: z.object({ + score: z.number().min(0).max(1), + hallucinations: z.array(z.string()) + }), + prompt: `Check if the answer contains information not in the sources. + +Sources: +${sources} + +Answer: ${output.answer} + +Score 0-1 for faithfulness. List any hallucinated claims.` + }); + + return { + success: true, + score: object.score, + metadata: { hallucinations: object.hallucinations } + }; + } +}); +``` + +**Key Pattern**: Three separate evals, each focused on one quality dimension. This makes it easy to identify specific issues (retrieval vs. generation vs. grounding) and allows independent scoring. + +## Error Handling + +Evals should handle errors gracefully to avoid breaking the eval pipeline. + +**Recommended Pattern:** + +```typescript +agent.createEval({ + metadata: { name: 'safe-external-check' }, + handler: async (ctx, input, output) => { + try { + const response = await fetch('https://api.example.com/validate', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ text: output.answer }), + signal: AbortSignal.timeout(3000) + }); + + if (!response.ok) { + return { success: false, error: `Service error: ${response.status}` }; + } + + const result = await response.json(); + return { success: true, passed: result.isValid }; + } catch (error) { + ctx.logger.error('External validation failed', { error: error.message }); + return { success: false, error: error.message }; + } + }, +}); +``` + +**Fallback Pattern:** + +```typescript +agent.createEval({ + metadata: { name: 'resilient-eval' }, + handler: async (ctx, input, output) => { + // Try primary method + try { + const result = await validateWithLLM(output.answer); + return { success: true, score: result.score, metadata: { method: 'llm' } }; + } catch (error) { + ctx.logger.warn('LLM validation failed, using fallback'); + } + + // Fallback to rule-based + try { + const score = calculateRuleBasedScore(output.answer); + return { success: true, score, metadata: { method: 'rules-fallback' } }; + } catch (error) { + return { success: false, error: 'All validation methods failed' }; + } + }, +}); +``` + +## How Evals Execute + +**Execution Timeline:** + +``` +1. HTTP request arrives + ↓ +2. Agent handler executes + ↓ +3. Output validated against schema + ↓ +4. Agent fires 'completed' event + ↓ +5. Evals scheduled via waitUntil() + │ + ├→ 6. Response returned immediately + │ + └→ 7. Evals execute asynchronously + ↓ + 8. Results sent to tracking service +``` + +**Key Points:** +- Evals run after the response is sent to the caller +- Uses `ctx.waitUntil()` to avoid blocking +- Each eval receives validated input/output +- Results are logged and tracked automatically + + +**Non-Blocking Design**: Evals execute asynchronously after the response is sent. Users get immediate responses while quality checks run in the background. + + +## Best Practices + +### When to Use Evals + +- **Quality assurance**: Validate completeness, relevance, format +- **Compliance**: Detect PII, check content policies +- **Performance**: Track execution time, resource usage +- **Testing**: A/B testing, regression testing + +### Eval Design + +**Keep evals focused on a single concern:** + +```typescript +// Good: Single-purpose +agent.createEval({ + metadata: { name: 'length-check' }, + handler: async (ctx, input, output) => { + return { success: true, passed: output.answer.length >= 50 }; + }, +}); + +// Avoid: Multiple concerns in one eval +agent.createEval({ + metadata: { name: 'everything-check' }, + handler: async (ctx, input, output) => { + const lengthOk = output.answer.length >= 50; + const hasKeywords = checkKeywords(output); + const sentiment = analyzeSentiment(output); + // Too many checks in one eval + }, +}); +``` + +**Use descriptive metadata:** + +```typescript +// Good +metadata: { + name: 'minimum-length-validation', + description: 'Ensures response meets 50 character minimum for quality' +} + +// Avoid +metadata: { name: 'check1' } +``` + +**Include helpful metadata in results:** + +```typescript +// Good: Detailed metadata +return { + success: true, + passed: false, + metadata: { + actualLength: output.answer.length, + minimumLength: 50, + deficit: 50 - output.answer.length, + reason: 'Response too short by 15 characters' + } +}; + +// Avoid: No context +return { success: true, passed: false }; +``` + +### Performance Considerations + +**Evals don't block responses:** +- Heavy computations are acceptable +- Network calls won't delay users +- Complex LLM evaluations can run without impact + +**Consider LLM costs:** +- LLM-as-judge evals consume API tokens +- Set appropriate budgets for high-traffic agents +- Consider caching common evaluations +- Use smaller models for simple checks + +### Performance Monitoring Pattern + +Track execution time and resource usage: + +```typescript +agent.createEval({ + metadata: { name: 'performance-monitor' }, + handler: async (ctx, input, output) => { + const startTime = ctx.state.get('startTime') as number; + const duration = startTime ? Date.now() - startTime : 0; + + // Store metrics + await ctx.kv.set('metrics', `perf-${ctx.sessionId}`, { + duration, + timestamp: Date.now(), + inputSize: JSON.stringify(input).length, + outputSize: JSON.stringify(output).length + }); + + return { + success: true, + passed: duration < 5000, + metadata: { durationMs: duration, threshold: 5000 } + }; + }, +}); +``` + +### A/B Testing Pattern + +Compare different approaches: + +```typescript +agent.createEval({ + metadata: { name: 'ab-test-tracker' }, + handler: async (ctx, input, output) => { + const variant = ctx.state.get('variant') as 'A' | 'B'; + if (!variant) { + return { success: false, error: 'No variant specified' }; + } + + let score = 0; + if (output.answer.length >= 100) score += 0.5; + if (output.confidence >= 0.8) score += 0.5; + + // Store for analysis + await ctx.kv.set('ab-test', `${variant}-${ctx.sessionId}`, { + variant, + score, + timestamp: Date.now() + }); + + return { success: true, score, metadata: { variant, testGroup: variant } }; + }, +}); +``` + +## Additional Resources + +- [API Reference](/SDKs/javascript/api-reference#evaluations) - Detailed type signatures and interfaces +- [Events Guide](/SDKs/javascript/events) - Monitor eval execution with event listeners +- [Schema Validation](/SDKs/javascript/schema-validation) - Learn more about input/output validation +- [Core Concepts](/SDKs/javascript/core-concepts) - Understanding agent architecture diff --git a/content/v1/Guides/events.mdx b/content/v1/Guides/events.mdx new file mode 100644 index 00000000..7d54ec80 --- /dev/null +++ b/content/v1/Guides/events.mdx @@ -0,0 +1,685 @@ +--- +title: Events +description: Lifecycle hooks for monitoring and extending agent behavior +--- + +Events provide lifecycle hooks for monitoring and extending agent behavior at three scopes: agent-level for individual executions, app-level for global tracking, and thread/session for state management. Use events for logging, metrics collection, analytics, and error tracking. All event listeners execute sequentially and support async operations. + +For more on sessions and threads, see [Core Concepts](/Introduction/core-concepts). For additional event examples, see the [Examples](/Examples) page. + +## Agent Events + +Agent events track the execution lifecycle of individual agents. Three events are available: `started` (when execution begins), `completed` (when execution finishes successfully), and `errored` (when an error occurs). Register listeners using `agent.addEventListener()` for agent-specific monitoring and validation. + +### Basic Agent Events + +Track agent execution with lifecycle events: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + 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}` }; + } +}); + +// Track when agent starts +agent.addEventListener('started', (eventName, agent, ctx) => { + ctx.state.set('startTime', Date.now()); + ctx.logger.info('Agent started', { + agentName: agent.metadata.name, + sessionId: ctx.sessionId + }); +}); + +// Track successful completion +agent.addEventListener('completed', (eventName, agent, ctx) => { + const startTime = ctx.state.get('startTime') as number; + const duration = Date.now() - startTime; + + ctx.logger.info('Agent completed', { + agentName: agent.metadata.name, + duration, + sessionId: ctx.sessionId + }); +}); + +// Track errors +agent.addEventListener('errored', (eventName, agent, ctx, error) => { + const startTime = ctx.state.get('startTime') as number; + const duration = Date.now() - startTime; + + ctx.logger.error('Agent failed', { + agentName: agent.metadata.name, + duration, + error: error.message, + sessionId: ctx.sessionId + }); +}); + +export default agent; +``` + +**Key Points:** +- Event listeners receive the event name, agent instance, and context +- `errored` listeners also receive the error object +- Use `ctx.state` to share data between event listeners +- Events execute sequentially in registration order + +### Agent Validation with Events + +Use the `completed` event to validate output quality without blocking execution: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ query: z.string() }), + output: z.object({ + answer: z.string(), + confidence: z.number() + }) + }, + handler: async (ctx, input) => { + // Process query and return result + return { + answer: `Answer to: ${input.query}`, + confidence: 0.85 + }; + } +}); + +// Validate output quality in completed event +agent.addEventListener('completed', (eventName, agent, ctx) => { + // Access the validated output from context state + const output = ctx.state.get('_evalOutput') as { answer: string; confidence: number }; + + // Check confidence threshold + if (output.confidence < 0.7) { + ctx.logger.warn('Low confidence output detected', { + confidence: output.confidence, + threshold: 0.7, + agentName: agent.metadata.name + }); + } + + // Check answer length + if (output.answer.length < 10) { + ctx.logger.warn('Suspiciously short answer', { + answerLength: output.answer.length, + agentName: agent.metadata.name + }); + } +}); + +export default agent; +``` + +**Key Points:** +- Validated input/output stored in `ctx.state` as `_evalInput` and `_evalOutput` +- Event listeners should log warnings, not throw errors +- Keep validation logic lightweight to avoid blocking + +For automated quality testing, see the [Evaluations Guide](/Guides/evaluations), which uses events internally to run after agent execution. + +## App-Level Events + +App-level events track all agents, sessions, and threads globally. These events fire automatically for all agent executions and lifecycle changes. Register app-level listeners in `app.ts` using `app.addEventListener()`. + +Available app-level events: +- `agent.started`, `agent.completed`, `agent.errored` - Track all agent executions +- `session.started`, `session.completed` - Track session lifecycle +- `thread.created`, `thread.destroyed` - Track thread lifecycle + +**Event Bubbling:** When an agent event fires, both the agent-level listeners and app-level listeners execute. This allows for both specific and global monitoring. + +### Global Agent Tracking + +Track all agent executions across your application: + +```typescript +import { createApp } from '@agentuity/runtime'; + +const app = createApp(); + +// Track execution counts per agent +const executionCounts = new Map(); + +app.addEventListener('agent.started', (eventName, agent, ctx) => { + const agentName = agent.metadata.name || 'unknown'; + const count = executionCounts.get(agentName) || 0; + executionCounts.set(agentName, count + 1); + + app.logger.info('Agent execution started', { + agent: agentName, + executionCount: count + 1, + session: ctx.sessionId + }); +}); + +app.addEventListener('agent.completed', (eventName, agent, ctx) => { + app.logger.info('Agent execution completed', { + agent: agent.metadata.name, + session: ctx.sessionId + }); +}); + +app.addEventListener('agent.errored', (eventName, agent, ctx, error) => { + app.logger.error('Agent execution failed', { + agent: agent.metadata.name, + error: error.message, + stack: error.stack, + session: ctx.sessionId + }); +}); + +export default app.server; +``` + +**Key Points:** +- App-level events provide global visibility into all executions +- Use for cross-cutting concerns like analytics and monitoring +- Event handlers have access to agent metadata and context + +### Session & Thread Lifecycle + +Monitor session and thread creation and completion: + +```typescript +import { createApp } from '@agentuity/runtime'; + +const app = createApp(); + +// Track active sessions +const activeSessions = new Map(); + +app.addEventListener('session.started', (eventName, session) => { + activeSessions.set(session.id, { + startTime: Date.now(), + requestCount: 0 + }); + + app.logger.info('Session started', { + sessionId: session.id, + threadId: session.thread.id + }); +}); + +app.addEventListener('session.completed', (eventName, session) => { + const sessionData = activeSessions.get(session.id); + + if (sessionData) { + const duration = Date.now() - sessionData.startTime; + + app.logger.info('Session completed', { + sessionId: session.id, + duration, + requestCount: sessionData.requestCount + }); + + activeSessions.delete(session.id); + } +}); + +app.addEventListener('thread.created', (eventName, thread) => { + app.logger.info('Thread created', { + threadId: thread.id + }); +}); + +app.addEventListener('thread.destroyed', (eventName, thread) => { + app.logger.info('Thread destroyed', { + threadId: thread.id + }); +}); + +export default app.server; +``` + +**Key Points:** +- Sessions and threads have their own lifecycle events +- Track session duration and request counts +- Threads are destroyed after 1 hour of inactivity + +## Thread & Session Events + +Thread and session instances provide their own event listeners for cleanup and state management. + +**Thread Events:** +- `destroyed` - Fired when `thread.destroy()` is called or thread expires + +**Session Events:** +- `completed` - Fired when session is saved + +Register listeners directly on thread or session instances within agent handlers. + +For detailed session and thread management patterns, see the [Sessions & Threads Guide](/Guides/sessions-threads). + +### Thread Cleanup + +Clean up resources when threads are destroyed: + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const agent = createAgent({ + handler: async (ctx, input) => { + // Register cleanup handler on first access + if (!ctx.thread.state.has('cleanupRegistered')) { + ctx.thread.addEventListener('destroyed', (eventName, thread) => { + ctx.logger.info('Cleaning up thread resources', { + threadId: thread.id, + messageCount: thread.state.get('messageCount') || 0 + }); + + // Clean up any thread-specific resources + thread.state.clear(); + }); + + ctx.thread.state.set('cleanupRegistered', true); + } + + // Track messages in this thread + const messageCount = (ctx.thread.state.get('messageCount') as number) || 0; + ctx.thread.state.set('messageCount', messageCount + 1); + + return { processed: true }; + } +}); + +export default agent; +``` + +**Key Points:** +- Thread `destroyed` event fires when thread expires or is manually destroyed +- Use for cleanup of thread-scoped resources +- Access thread state in event listeners + +### Session State Persistence + +Save session state when sessions complete: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ action: z.string() }) + }, + handler: async (ctx, input) => { + // Register session completion handler once + if (!ctx.session.state.has('persistenceRegistered')) { + ctx.session.addEventListener('completed', async (eventName, session) => { + // Save session metrics to KV storage + const metrics = { + totalRequests: session.state.get('totalRequests') || 0, + lastAction: session.state.get('lastAction'), + duration: Date.now() - (session.state.get('startTime') as number || Date.now()) + }; + + await ctx.kv.set('session-metrics', session.id, metrics, { + ttl: 86400 // 24 hours + }); + + ctx.logger.info('Session metrics saved', { + sessionId: session.id, + metrics + }); + }); + + ctx.session.state.set('persistenceRegistered', true); + ctx.session.state.set('startTime', Date.now()); + } + + // Track session activity + const totalRequests = (ctx.session.state.get('totalRequests') as number) || 0; + ctx.session.state.set('totalRequests', totalRequests + 1); + ctx.session.state.set('lastAction', input.action); + + return { success: true }; + } +}); + +export default agent; +``` + +**Key Points:** +- Session `completed` event fires when session is saved +- Use for persisting session data to storage +- Register event listeners once using a flag in session state + +## Common Patterns + +### Request Timing & Performance Monitoring + +Track agent execution time and identify slow operations: + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const agent = createAgent({ + handler: async (ctx, input) => { + // Process request + await new Promise(resolve => setTimeout(resolve, 1200)); + return { result: 'completed' }; + } +}); + +agent.addEventListener('started', (eventName, agent, ctx) => { + ctx.state.set('performanceStart', performance.now()); +}); + +agent.addEventListener('completed', (eventName, agent, ctx) => { + const startTime = ctx.state.get('performanceStart') as number; + const duration = performance.now() - startTime; + + // Log slow executions + if (duration > 1000) { + ctx.logger.warn('Slow agent execution detected', { + agentName: agent.metadata.name, + duration, + threshold: 1000, + sessionId: ctx.sessionId + }); + } + + ctx.logger.info('Agent execution time', { + agentName: agent.metadata.name, + duration + }); +}); + +export default agent; +``` + +### Error Tracking & Aggregation + +Capture and aggregate errors across all agents: + +```typescript +import { createApp } from '@agentuity/runtime'; + +const app = createApp(); + +// Track error counts by type +const errorCounts = new Map(); + +app.addEventListener('agent.errored', async (eventName, agent, ctx, error) => { + const errorType = error.name || 'UnknownError'; + const count = errorCounts.get(errorType) || 0; + errorCounts.set(errorType, count + 1); + + // Log detailed error information + app.logger.error('Agent error captured', { + agentName: agent.metadata.name, + errorType, + errorMessage: error.message, + errorCount: count + 1, + sessionId: ctx.sessionId, + stack: error.stack + }); + + // Store error log in KV storage + const errorLog = { + timestamp: new Date().toISOString(), + agentName: agent.metadata.name, + errorType, + message: error.message, + sessionId: ctx.sessionId + }; + + const errorKey = `error-${Date.now()}-${Math.random().toString(36).slice(2)}`; + await ctx.kv.set('error-logs', errorKey, errorLog, { + ttl: 604800 // 7 days + }); +}); + +export default app.server; +``` + +### Analytics & Usage Tracking + +Track agent usage patterns and session metrics: + +```typescript +import { createApp } from '@agentuity/runtime'; + +const app = createApp(); + +// Track agent invocations +app.addEventListener('agent.completed', async (eventName, agent, ctx) => { + const agentName = agent.metadata.name || 'unknown'; + const today = new Date().toISOString().split('T')[0]; + const metricsKey = `agent-metrics-${today}-${agentName}`; + + // Get current count + const result = await ctx.kv.get('analytics', metricsKey); + let count = 0; + + if (result.exists) { + const data = await result.data.json(); + count = data.count || 0; + } + + // Increment and store + await ctx.kv.set('analytics', metricsKey, { + count: count + 1, + agentName, + date: today + }, { + ttl: 2592000 // 30 days + }); +}); + +// Track session durations +app.addEventListener('session.completed', async (eventName, session) => { + const startTime = session.state.get('sessionStart') as number; + + if (startTime) { + const duration = Date.now() - startTime; + + app.logger.info('Session analytics', { + sessionId: session.id, + duration, + requestCount: session.state.get('requestCount') || 0 + }); + } +}); + +export default app.server; +``` + +### Audit Logging + +Log all agent executions for compliance and debugging: + +```typescript +import { createApp } from '@agentuity/runtime'; + +const app = createApp(); + +app.addEventListener('agent.started', async (eventName, agent, ctx) => { + const auditLog = { + timestamp: new Date().toISOString(), + event: 'agent.started', + agentName: agent.metadata.name, + agentId: agent.metadata.id, + sessionId: ctx.sessionId, + agentVersion: agent.metadata.version + }; + + // Store in object storage for long-term retention + const logKey = `${auditLog.timestamp}-${ctx.sessionId}-${agent.metadata.name}.json`; + + await ctx.objectstore.put('audit-logs', logKey, auditLog, { + contentType: 'application/json', + metadata: { + sessionId: ctx.sessionId, + agentName: agent.metadata.name, + timestamp: auditLog.timestamp + } + }); +}); + +app.addEventListener('agent.completed', async (eventName, agent, ctx) => { + const auditLog = { + timestamp: new Date().toISOString(), + event: 'agent.completed', + agentName: agent.metadata.name, + sessionId: ctx.sessionId + // Note: Do not log full input/output for privacy + }; + + const logKey = `${auditLog.timestamp}-${ctx.sessionId}-${agent.metadata.name}-completed.json`; + + await ctx.objectstore.put('audit-logs', logKey, auditLog, { + contentType: 'application/json' + }); +}); + +export default app.server; +``` + +**Key Points:** +- Store audit logs in object storage for long-term retention +- Include timestamps, agent metadata, and session IDs +- Avoid logging sensitive data (full input/output) +- Use metadata for filtering and searching + +See the [API Reference](/api-reference#key-value-storage) for complete documentation on storage APIs (KV, Vector, ObjectStore) and streaming. + +## Best Practices + + +**Event Handler Guidelines** + +- **Keep handlers lightweight** - Event listeners should complete quickly to avoid blocking execution +- **Use `ctx.waitUntil()`** - For non-blocking background work, use `ctx.waitUntil()` to defer processing until after the response is sent +- **Don't modify request flow** - Event handlers should not throw errors to stop execution or modify the response +- **Sequential execution** - Event listeners execute in registration order, one at a time +- **Error handling** - Errors in event handlers are logged but don't stop execution or affect other listeners +- **Use appropriate scope** - Use app-level events for cross-cutting concerns, agent-level events for agent-specific logic +- **Avoid side effects** - Event handlers should primarily observe and log, not modify state that affects business logic + + +### Using waitUntil in Events + +Use `ctx.waitUntil()` to perform heavy work without blocking the response: + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const agent = createAgent({ + handler: async (ctx, input) => { + return { result: 'processed' }; + } +}); + +agent.addEventListener('completed', (eventName, agent, ctx) => { + // Use waitUntil for non-blocking background work + ctx.waitUntil(async () => { + // Simulate sending metrics to external service + await new Promise(resolve => setTimeout(resolve, 500)); + + ctx.logger.info('Metrics sent to external service', { + agentName: agent.metadata.name, + sessionId: ctx.sessionId + }); + }); + + // This logs immediately + ctx.logger.info('Agent completed (handler finished)', { + agentName: agent.metadata.name + }); +}); + +export default agent; +``` + +**Key Points:** +- `waitUntil()` executes after the response is sent to the client +- Use for analytics, metrics, or external API calls +- Prevents blocking the user-facing response +- Multiple `waitUntil()` calls can run concurrently + +## Event Execution Order + +Events execute in a predictable sequence: + +1. Agent `started` event fires +2. App `agent.started` event fires +3. Agent handler executes +4. Agent `completed` event fires (or `errored` if exception occurs) +5. App `agent.completed` event fires (or `agent.errored`) + +All event listeners execute sequentially. If an agent-level `completed` listener and an app-level `agent.completed` listener are both registered, the agent-level listener executes first. + +### Event Order Demonstration + +Observe event execution order with timestamps: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { createApp } from '@agentuity/runtime'; + +// In app.ts +const app = createApp(); + +app.addEventListener('agent.started', (eventName, agent, ctx) => { + console.log(`[${Date.now()}] App: agent.started - ${agent.metadata.name}`); +}); + +app.addEventListener('agent.completed', (eventName, agent, ctx) => { + console.log(`[${Date.now()}] App: agent.completed - ${agent.metadata.name}`); +}); + +export default app.server; + +// In agent.ts +const agent = createAgent({ + handler: async (ctx, input) => { + console.log(`[${Date.now()}] Agent: handler executing`); + await new Promise(resolve => setTimeout(resolve, 100)); + return { result: 'done' }; + } +}); + +agent.addEventListener('started', (eventName, agent, ctx) => { + console.log(`[${Date.now()}] Agent: started event`); +}); + +agent.addEventListener('completed', (eventName, agent, ctx) => { + console.log(`[${Date.now()}] Agent: completed event`); +}); + +export default agent; +``` + +**Expected Output:** +``` +[1234567890] Agent: started event +[1234567891] App: agent.started +[1234567892] Agent: handler executing +[1234567992] Agent: completed event +[1234567993] App: agent.completed +``` + +**Key Points:** +- Agent-level events fire before app-level events +- All listeners execute sequentially (blocking) +- Handler execution occurs between `started` and `completed` events +- Event timing is predictable and deterministic diff --git a/content/v1/Guides/key-value-storage.mdx b/content/v1/Guides/key-value-storage.mdx new file mode 100644 index 00000000..b9b8513a --- /dev/null +++ b/content/v1/Guides/key-value-storage.mdx @@ -0,0 +1,277 @@ +--- +title: Key-Value Storage +description: Fast, ephemeral storage for agent state, configuration, and caching +--- + +# Key-Value Storage + +Key-value storage provides fast, ephemeral data access for agents. Use it for session state, configuration, caching, and temporary data that needs quick lookups. + +## When to Use Key-Value Storage + +Key-value storage is your go-to solution for fast, ephemeral data that agents need to access quickly. Think of it as your agent's short-term memory - perfect for session state, configuration, caching, and temporary data. + +Choose the right storage for your use case: + +- **Key-Value**: Fast lookups, simple data, temporary state +- **Vector Storage**: Semantic search, embeddings, similarity matching +- **Object Storage**: Large files, media, backups + +## Common Use Cases + +- **Persistent User Data**: Store user profiles, "remember me" tokens, and preferences that persist beyond active session lifetimes (use `ctx.session.state` for active session data - see [Sessions and Threads](/Guides/sessions-threads)) +- **Configuration Storage**: Keep agent-specific settings, feature flags, and runtime configuration that can be updated without redeployment +- **Caching**: Cache expensive computation results, API responses, or frequently accessed data to improve agent performance +- **Inter-Agent Communication**: Share state between agents working together on complex workflows +- **Rate Limiting**: Track API usage, request counts, and implement throttling mechanisms + + +v1 provides built-in state management (`ctx.state`, `ctx.thread.state`, `ctx.session.state`) for data tied to active requests, conversations, and sessions. Use KV when you need: + +- **Custom TTL** longer than session/thread lifetimes +- **Persistent data** across sessions (user profiles, settings) +- **Shared state** across multiple sessions or agents (global config, rate limits) + +See [Sessions and Threads](/Guides/sessions-threads) for built-in state management. + + +## Creating Key-Value Storage + +You can create key-value storage either through the Cloud Console or programmatically in your agent code. + +### Via Cloud Console + +Navigate to **Services > Key Value** and click **Create Storage**. Choose a descriptive name that reflects the storage purpose (e.g., `user-sessions`, `agent-config`, `api-cache`). + + + +### Via SDK + +The SDK automatically creates key-value storage when you first access it: + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const agent = createAgent({ + handler: async (ctx, input) => { + // Buckets are auto-created if they don't exist + await ctx.kv.set('user-sessions', 'user-123', { + lastSeen: new Date().toISOString(), + preferences: { theme: 'dark' } + }); + + return { message: 'Session stored' }; + } +}); +``` + +## Working with Key-Value Storage + +The key-value API provides three core operations: `get`, `set`, and `delete`. All operations are asynchronous and support various data types. + +The first parameter in all operations is the storage bucket name (also called "name" or "namespace"). Buckets are automatically created when you first use them. + +### Storing Data + +Store strings, objects, or binary data. Keys persist indefinitely by default, or you can set a TTL (time-to-live) for automatic expiration: + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + // Simple store + await ctx.kv.set('cache', 'api-response', responseData); + + // Store an object + await ctx.kv.set('user-prefs', input.userId, { + language: 'en', + timezone: 'UTC' + }); + + // Store with TTL (expires after 1 hour) + await ctx.kv.set('sessions', input.sessionId, userData, { + ttl: 3600 // seconds + }); + + // Store feature flags (no TTL - persistent config) + await ctx.kv.set('feature-flags', 'beta-features', { + darkMode: true, + aiAssistant: false, + newDashboard: true + }); + + return { success: true }; + } +}); +``` + +### Retrieving Data + +Retrieve stored values with automatic deserialization: + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + // Get a value + const result = await ctx.kv.get('user-prefs', input.userId); + + if (result.exists) { + // Data is already deserialized + const preferences = result.data; + ctx.logger.info('User preferences:', preferences); + } else { + ctx.logger.info('No preferences found for user'); + } + + // Handle missing keys gracefully + const configResult = await ctx.kv.get('config', 'app-settings'); + const config = configResult.exists ? configResult.data : defaultConfig; + + return { config }; + } +}); +``` + +**Type Safety with TypeScript:** + +Use type parameters for full type safety: + +```typescript +interface UserPreferences { + language: string; + timezone: string; + theme: 'light' | 'dark'; +} + +const agent = createAgent({ + handler: async (ctx, input) => { + // Specify type for type-safe access + const result = await ctx.kv.get('user-prefs', input.userId); + + if (result.exists) { + // TypeScript knows the shape of result.data + const theme = result.data.theme; // Type: 'light' | 'dark' + const language = result.data.language; // Type: string + } + + return { success: true }; + } +}); +``` + +### Deleting Data + +Remove keys when they're no longer needed: + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + // Delete a single key + await ctx.kv.delete('sessions', input.sessionId); + + ctx.logger.info('Session deleted successfully'); + + return { success: true }; + } +}); +``` + +## Best Practices + +### Key Naming Conventions + +Use hierarchical, descriptive keys to organize your data: +- `user:{userId}:preferences` +- `session:{sessionId}:data` +- `cache:api:{endpoint}:{params}` + +### Error Handling + +Always handle potential storage errors gracefully: + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + try { + const result = await ctx.kv.get('config', 'settings'); + + if (!result.exists) { + // Initialize with defaults + await ctx.kv.set('config', 'settings', defaultSettings); + } + + return { config: result.exists ? result.data : defaultSettings }; + } catch (error) { + ctx.logger.error('Storage error:', error); + + // Fall back to in-memory defaults + return { config: defaultSettings }; + } + } +}); +``` + +### TTL Strategy + +Keys persist indefinitely by default unless you specify a TTL (time-to-live) value. When you do specify a TTL, the minimum allowed value is 60 seconds. + +Use TTL for temporary data to prevent storage bloat: +- **Session data**: 24-48 hours (86400-172800 seconds) +- **API cache**: 5-60 minutes (300-3600 seconds) +- **Rate-limiting counters**: Until period reset +- **Permanent config**: No TTL (keys persist forever) + +Example with appropriate TTLs: + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + // Session data - expires after 24 hours + await ctx.kv.set('sessions', input.sessionId, sessionData, { + ttl: 86400 + }); + + // API cache - expires after 5 minutes + await ctx.kv.set('cache', cacheKey, apiResponse, { + ttl: 300 + }); + + // Rate limit counter - expires at end of hour + const secondsUntilHourEnd = 3600 - (Date.now() / 1000 % 3600); + await ctx.kv.set('rate-limits', input.userId, requestCount, { + ttl: Math.ceil(secondsUntilHourEnd) + }); + + // Feature flags - no TTL (persistent) + await ctx.kv.set('feature-flags', 'global', featureFlags); + + return { success: true }; + } +}); +``` + +### Data Size Considerations + +Key-value storage is optimized for small to medium-sized values. For large files or documents, consider using Object Storage instead. + +## Monitoring Usage + +Track your key-value storage usage through the Cloud Console: + +1. Navigate to **Services > Key Value** +2. View storage size and record count for each instance +3. Click on an instance to browse stored keys and values +4. Monitor agent telemetry for KV operation performance + + + +For more complex data relationships or query needs, consider combining storage types or using external databases through your agent. + +## Storage Types Overview + +TODO: Add video for v1 storage overview + +## Next Steps + +- [Agent Communication](/Guides/agent-communication) - Share state between agents +- [Sessions and Threads](/Guides/sessions-threads) - Manage conversation state +- [API Reference](/SDK/api-reference) - Complete storage API documentation diff --git a/content/v1/Guides/routing-triggers.mdx b/content/v1/Guides/routing-triggers.mdx new file mode 100644 index 00000000..cd7adbec --- /dev/null +++ b/content/v1/Guides/routing-triggers.mdx @@ -0,0 +1,476 @@ +--- +title: Routing & Triggers +description: Define HTTP endpoints, scheduled jobs, email handlers, SMS receivers, and more - all from within your agent code +--- + +Routes define how your application responds to different HTTP requests and triggers. The SDK provides HTTP routing and specialized routes for email, WebSocket, scheduled jobs, and other event sources. + + +All triggers are defined in your codebase (`route.ts` files) and are automatically discovered by the SDK. This provides: + +- **Version control** - Routes are tracked in git alongside your agent code +- **Type safety** - TypeScript validates route configurations at compile time +- **Code review** - Changes to triggers are visible in pull requests +- **Testability** - Routes can be unit tested like any other code +- **UI visibility** - Configured routes are displayed in the Agentuity dashboard for monitoring + +No manual UI configuration required. Define routes directly in your agent code, deploy, and they're immediately available. + + +## HTTP Routes + +Routes are created using `createRouter()` and map URL patterns to handler functions. + +### Basic Routes + +```typescript +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +router.get('/', async (c) => { + return c.json({ message: 'Hello' }); +}); + +router.post('/users', async (c) => { + const body = await c.req.json(); + return c.json({ created: true, data: body }); +}); + +export default router; +``` + +### HTTP Methods + +The router supports standard HTTP methods: `get`, `post`, `put`, `patch`, `delete`, `options`. + +```typescript +router.get('/data', async (c) => c.json({ action: 'read' })); +router.post('/data', async (c) => c.json({ action: 'create' })); +router.put('/data', async (c) => c.json({ action: 'update' })); +router.delete('/data', async (c) => c.json({ action: 'delete' })); +``` + +### Route Parameters + +Capture variable segments in URLs using `:paramName`: + +```typescript +// Single parameter +router.get('/users/:id', async (c) => { + const id = c.req.param('id'); + return c.json({ userId: id }); +}); + +// Multiple parameters +router.get('/posts/:year/:month/:slug', async (c) => { + const year = c.req.param('year'); + const month = c.req.param('month'); + const slug = c.req.param('slug'); + return c.json({ year, month, slug }); +}); +``` + +### Query Parameters + +Access query strings using `c.req.query()`: + +```typescript +router.get('/search', async (c) => { + const query = c.req.query('q'); + const page = c.req.query('page') || '1'; + const limit = c.req.query('limit') || '10'; + + return c.json({ query, page, limit }); +}); +// URL: /search?q=hello&page=2 +// Returns: { "query": "hello", "page": "2", "limit": "10" } +``` + +**Route parameters vs query strings:** +- **Route parameters** (`:id`): Part of the URL structure, typically required +- **Query strings** (`?page=2`): Optional filters or settings + +### Calling Agents from Routes + +Routes can invoke agents using the agent registry: + +```typescript +router.post('/process', async (c) => { + const input = await c.req.json(); + + const result = await c.agent.processorAgent.run({ + data: input.data + }); + + return c.json(result); +}); +``` + +### Validation + +Use `zValidator` middleware to validate request input: + +```typescript +import { zValidator } from '@hono/zod-validator'; +import { z } from 'zod'; + +const schema = z.object({ + name: z.string().min(1), + email: z.string().email() +}); + +router.post('/users', + zValidator('json', schema), + async (c) => { + const data = c.req.valid('json'); // Type-safe, validated data + return c.json({ success: true, data }); + } +); +``` + +## Specialized Routes + +Define triggers for email, SMS, scheduled jobs, WebSocket connections, and real-time streams - all from within your agent code. These specialized routes enable your agents to respond to diverse event sources without any UI configuration. + +### Email Routes + +Handle incoming emails sent to a specific address. + +**Signature:** `router.email(address: string, handler: EmailHandler): Router` + +```typescript +router.email('support@example.com', async (email, c) => { + c.logger.info('Email received', { + from: email.from, + subject: email.subject + }); + + const result = await c.agent.emailProcessor.run({ + sender: email.from, + content: email.text || email.html || '' + }); + + return c.json({ processed: true }); +}); +``` + +**Email object structure:** +```typescript +interface Email { + from: string; + to: string[]; + subject: string; + text?: string; + html?: string; + headers: Record; + attachments?: Array<{ + filename: string; + contentType: string; + content: Buffer; + }>; +} +``` + +### WebSocket Routes + +Create WebSocket endpoints for real-time bidirectional communication. + +**Signature:** `router.websocket(path: string, handler: WebSocketHandler): Router` + +```typescript +router.websocket('/chat', (c) => (ws) => { + ws.onOpen((event) => { + c.logger.info('WebSocket connected'); + ws.send(JSON.stringify({ type: 'connected' })); + }); + + ws.onMessage(async (event) => { + const message = JSON.parse(event.data); + + const response = await c.agent.chatAgent.run({ + message: message.text + }); + + ws.send(JSON.stringify({ type: 'response', data: response })); + }); + + ws.onClose((event) => { + c.logger.info('WebSocket disconnected'); + }); +}); +``` + +**WebSocket connection methods:** +- `onOpen(handler)`: Called when connection opens +- `onMessage(handler)`: Called when message received +- `onClose(handler)`: Called when connection closes +- `send(data)`: Send data to client + +### Server-Sent Events (SSE) + +Create SSE endpoints for server-to-client streaming. + +**Signature:** `router.sse(path: string, handler: SSEHandler): Router` + +```typescript +router.sse('/updates', (c) => async (stream) => { + await stream.write({ type: 'connected' }); + + // Stream agent progress updates + const updates = await c.agent.longRunningAgent.run({ task: 'process' }); + + for (const update of updates) { + await stream.write({ + type: 'progress', + data: update + }); + } + + stream.onAbort(() => { + c.logger.info('Client disconnected'); + }); +}); +``` + +**SSE stream methods:** +- `write(data)`: Send data as JSON +- `writeSSE(message)`: Send raw SSE message with event/id +- `onAbort(handler)`: Called when client disconnects +- `close()`: Close the stream + +### Stream Routes + +Create HTTP streaming endpoints for piping data streams. + +**Signature:** `router.stream(path: string, handler: StreamHandler): Router` + +```typescript +router.stream('/data', async (c) => { + const stream = new ReadableStream({ + async start(controller) { + const data = await c.agent.dataGenerator.run({ query: 'all' }); + + for (const chunk of data) { + controller.enqueue( + new TextEncoder().encode(JSON.stringify(chunk) + '\n') + ); + } + + controller.close(); + } + }); + + return stream; +}); +``` + +### Cron Routes + +Schedule recurring jobs using cron syntax. + +**Signature:** `router.cron(schedule: string, handler: CronHandler): Router` + +```typescript +// Run daily at 9am +router.cron('0 9 * * *', async (c) => { + c.logger.info('Running daily report'); + + const report = await c.agent.reportGenerator.run({ + type: 'daily', + date: new Date().toISOString() + }); + + await c.kv.set('reports', `daily-${Date.now()}`, report); + + return c.json({ success: true }); +}); + +// Run every 5 minutes +router.cron('*/5 * * * *', async (c) => { + await c.agent.healthCheck.run({}); + return c.json({ checked: true }); +}); +``` + +**Cron schedule format:** +``` +┌───────────── minute (0-59) +│ ┌───────────── hour (0-23) +│ │ ┌───────────── day of month (1-31) +│ │ │ ┌───────────── month (1-12) +│ │ │ │ ┌───────────── day of week (0-6, Sunday=0) +│ │ │ │ │ +* * * * * +``` + +**More examples:** +- `0 9 * * *` - Daily at 9am +- `*/5 * * * *` - Every 5 minutes +- `0 0 * * 0` - Weekly on Sunday at midnight +- `0 0 1 * *` - Monthly on the 1st at midnight + +### SMS Routes + +Handle incoming SMS messages sent to a specific phone number. + +**Signature:** `router.sms(params: { number: string }, handler: SMSHandler): Router` + +```typescript +router.sms({ number: '+12345678900' }, async (c) => { + const body = await c.req.json(); + + c.logger.info('SMS received', { + from: body.from, + message: body.text + }); + + const response = await c.agent.smsBot.run({ + sender: body.from, + message: body.text + }); + + return c.json({ reply: response }); +}); +``` + +Phone numbers must use E.164 format (e.g., `+12345678900`). + +## Route Organization + +### Agent Route Pattern + +Each agent typically has two files that work together: + +**agent.ts:** +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ message: z.string() }), + output: z.string() + }, + handler: async (ctx, input) => { + return `Received: ${input.message}`; + } +}); + +export default agent; +``` + +**route.ts:** +```typescript +import { createRouter } from '@agentuity/runtime'; +import { zValidator } from '@hono/zod-validator'; +import agent from './agent'; + +const router = createRouter(); + +// GET route for easy testing +router.get('/', async (c) => { + const result = await c.agent.myAgent.run({ message: 'Test' }); + return c.text(result); +}); + +// POST route with validation +router.post('/', + zValidator('json', agent.inputSchema!), + async (c) => { + const data = c.req.valid('json'); + const result = await c.agent.myAgent.run(data); + return c.text(result); + } +); + +export default router; +``` + +The GET route provides an easy way to test the agent in a browser, while the POST route is the primary endpoint with input validation. + +### Request Context + +The context object (`c`) provides access to request data and Agentuity services: + +**Request data:** +```typescript +c.req.json() // Parse JSON body +c.req.text() // Get text body +c.req.param('id') // Get route parameter +c.req.query('page') // Get query string +c.req.header('Auth') // Get request header +``` + +**Responses:** +```typescript +c.json(data) // Send JSON response +c.text(string) // Send text response +c.html(markup) // Send HTML response +c.redirect(url) // Redirect to URL +``` + +**Agentuity services:** +```typescript +c.agent.name.run() // Call another agent +c.kv.get/set/delete() // Key-value storage +c.vector.search() // Vector database +c.logger.info() // Structured logging +c.objectstore.* // Object storage +``` + +## Best Practices + +### Validate All Input + +Always validate request input using schemas: + +```typescript +// Define schema +const schema = z.object({ + email: z.string().email(), + age: z.number().min(0) +}); + +// Use in route +router.post('/endpoint', + zValidator('json', schema), + async (c) => { + const data = c.req.valid('json'); // Guaranteed valid + // ... + } +); +``` + +### Use Structured Logging + +Use `c.logger` (not `console.log`) for searchable, traceable logs: + +```typescript +c.logger.info('User created', { userId, timestamp: Date.now() }); +c.logger.error('Failed to process', { error: err.message }); +``` + +### Export Router as Default + +Route files must export the router as the default export: + +```typescript +const router = createRouter(); +// ... define routes ... +export default router; +``` + +### Route Order Matters + +Register specific routes before generic ones: + +```typescript +// Correct order +router.get('/users/admin', adminHandler); +router.get('/users/:id', userHandler); + +// Wrong - :id matches everything including "admin" +router.get('/users/:id', userHandler); +router.get('/users/admin', adminHandler); // Never reached +``` diff --git a/content/v1/Guides/schema-validation.mdx b/content/v1/Guides/schema-validation.mdx new file mode 100644 index 00000000..eba83ae8 --- /dev/null +++ b/content/v1/Guides/schema-validation.mdx @@ -0,0 +1,711 @@ +--- +title: Schema Validation +description: Type-safe input and output validation for agents +--- + +Schema validation provides runtime type safety and automatic validation for your agents. By defining schemas, you ensure data integrity, get full TypeScript autocomplete, and catch errors before they reach production. + +## Why Use Schema Validation + +**Runtime Safety:** +- Validates input before your handler runs +- Validates output before returning to callers +- Prevents invalid data from causing runtime errors + +**Type Safety:** +- Full TypeScript autocomplete in your IDE +- Compile-time type checking +- No need to manually define TypeScript interfaces + +**Documentation:** +- Schemas serve as clear documentation of expected inputs and outputs +- Self-documenting API contracts + +## StandardSchema + +The SDK uses the StandardSchemaV1 interface, which allows different validation libraries to work seamlessly together. + +### Supported Libraries + +**Zod** - Most popular, and most common in Agentuity examples: +```typescript +import { z } from 'zod'; +``` + +**Valibot** - Lightweight alternative with similar API: +```typescript +import * as v from 'valibot'; +``` + +**ArkType** - TypeScript-first validation: +```typescript +import { type } from 'arktype'; +``` + + +Any library implementing StandardSchemaV1 will work with the SDK. + + +### Why StandardSchema? + +StandardSchemaV1 is a common interface that provides: +- **Library independence** - Not locked into a single validation library +- **Flexibility** - Use the library that best fits your needs +- **Future-proof** - New libraries work automatically if they implement the standard + +## Defining Schemas + +Schemas are defined in the `schema` property of `createAgent()`. + +### Input Schemas + +Input schemas validate data passed to your agent: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ + email: z.string().email(), + age: z.number().min(0).max(120), + preferences: z.object({ + newsletter: z.boolean(), + notifications: z.boolean(), + }).optional(), + }), + }, + handler: async (ctx, input) => { + // input is validated before this runs + // TypeScript knows the exact shape of input + ctx.logger.info(`User email: ${input.email}`); + return { success: true }; + }, +}); +``` + +### Output Schemas + +Output schemas validate data returned from your agent: + +```typescript +const agent = createAgent({ + schema: { + output: z.object({ + userId: z.string().uuid(), + created: z.date(), + status: z.enum(['active', 'pending']), + }), + }, + handler: async (ctx, input) => { + return { + userId: crypto.randomUUID(), + created: new Date(), + status: 'active' + }; + // Output is validated before returning to caller + }, +}); +``` + +### Combined Input and Output + +Define both input and output schemas for complete validation: + +```typescript +const agent = createAgent({ + schema: { + input: z.object({ + query: z.string(), + limit: z.number().default(10), + }), + output: z.object({ + results: z.array(z.string()), + total: z.number(), + }), + }, + handler: async (ctx, input) => { + // Both input and output are validated + return { + results: ['item1', 'item2'], + total: 2, + }; + }, +}); +``` + +### Optional Schemas + +Schemas are optional. If not provided, no validation occurs: + +```typescript +const agent = createAgent({ + // No schema defined + handler: async (ctx, input) => { + // input is unknown type + // No runtime validation + return { message: 'Done' }; + }, +}); +``` + +For type safety, it's recommended to add a schema. + +## Type Inference + +TypeScript automatically infers types from your schemas, providing full autocomplete and compile-time type checking. + +### Inferred Input Types + +```typescript +const agent = createAgent({ + schema: { + input: z.object({ + query: z.string(), + filters: z.object({ + category: z.enum(['tech', 'business', 'sports']), + limit: z.number().default(10), + }), + }), + }, + handler: async (ctx, input) => { + // TypeScript knows: + // - input.query is string + // - input.filters.category is 'tech' | 'business' | 'sports' + // - input.filters.limit is number + + const category = input.filters.category; // Full autocomplete! + return { processed: true }; + }, +}); +``` + +### Inferred Output Types + +```typescript +const agent = createAgent({ + schema: { + output: z.object({ + results: z.array(z.object({ + id: z.string(), + title: z.string(), + score: z.number(), + })), + total: z.number(), + }), + }, + handler: async (ctx, input) => { + // Return type is validated and type-checked + return { + results: [ + { id: '1', title: 'Example', score: 0.95 }, + ], + total: 1, + }; + + // This would cause a TypeScript error: + // return { invalid: 'structure' }; + }, +}); +``` + +### Calling Agents with Type Inference + +When calling agents, TypeScript knows the input and output types: + +```typescript +// When calling the agent from another agent: +const result = await ctx.agent.searchAgent.run({ + query: 'agentic AI', + filters: { category: 'tech', limit: 5 }, +}); + +// TypeScript knows result has this shape: +// { +// results: Array<{ id: string; title: string; score: number }>; +// total: number; +// } + +console.log(result.results[0].title); // Full autocomplete! +``` + +**Benefits:** +- Full IDE autocomplete for input and output +- Compile-time type checking catches errors before runtime +- No need to manually define TypeScript interfaces +- Refactoring is safer - changes to schemas update types automatically + +## Validation Libraries + +### Zod + +[Zod](https://zod.dev/) is the most popular validation library with excellent TypeScript support. + +```typescript +import { z } from 'zod'; + +const schema = z.object({ + name: z.string().min(1), + email: z.string().email(), + age: z.number().min(18), + role: z.enum(['admin', 'user', 'guest']), + tags: z.array(z.string()).optional(), +}); +``` + +**Common Zod patterns:** +```typescript +// String validation +z.string() // Any string +z.string().min(5) // Minimum length +z.string().max(100) // Maximum length +z.string().email() // Email format +z.string().url() // URL format +z.string().uuid() // UUID format + +// Number validation +z.number() // Any number +z.number().min(0) // Minimum value +z.number().max(100) // Maximum value +z.number().int() // Integer only +z.number().positive() // Positive numbers + +// Boolean, null, undefined +z.boolean() // Boolean +z.null() // Null +z.undefined() // Undefined + +// Arrays and objects +z.array(z.string()) // Array of strings +z.object({ name: z.string() }) // Object shape +z.record(z.string()) // Record/map + +// Optional and nullable +z.string().optional() // string | undefined +z.string().nullable() // string | null +z.string().nullish() // string | null | undefined + +// Defaults +z.string().default('hello') // Use default if undefined +z.number().default(0) // Use default if undefined + +// Enums and literals +z.enum(['a', 'b', 'c']) // One of these strings +z.literal('exact') // Exact value +z.union([z.string(), z.number()]) // Multiple types +``` + +### Valibot + +[Valibot](https://valibot.dev/) is a lightweight alternative with a similar API. + +```typescript +import * as v from 'valibot'; + +const schema = v.object({ + name: v.string([v.minLength(1)]), + email: v.string([v.email()]), + age: v.number([v.minValue(18)]), + role: v.union([ + v.literal('admin'), + v.literal('user'), + v.literal('guest') + ]), +}); +``` + +**Common Valibot patterns:** +```typescript +// String validation +v.string() // Any string +v.string([v.minLength(5)]) // Minimum length +v.string([v.maxLength(100)]) // Maximum length +v.string([v.email()]) // Email format +v.string([v.url()]) // URL format + +// Number validation +v.number() // Any number +v.number([v.minValue(0)]) // Minimum value +v.number([v.maxValue(100)]) // Maximum value +v.number([v.integer()]) // Integer only + +// Arrays and objects +v.array(v.string()) // Array of strings +v.object({ name: v.string() }) // Object shape + +// Optional and nullable +v.optional(v.string()) // string | undefined +v.nullable(v.string()) // string | null +v.nullish(v.string()) // string | null | undefined +``` + +### ArkType + +[ArkType](https://arktype.io/) provides a TypeScript-first approach to validation. + +```typescript +import { type } from 'arktype'; + +const schema = type({ + name: 'string>0', + email: 'string.email', + age: 'number>=18', + role: '"admin"|"user"|"guest"', + tags: 'string[]?', +}); +``` + +**Common ArkType patterns:** +```typescript +// String validation +type('string') // Any string +type('string>5') // Minimum length +type('string<100') // Maximum length +type('string.email') // Email format +type('string.url') // URL format + +// Number validation +type('number') // Any number +type('number>0') // Greater than 0 +type('number<=100') // Less than or equal to 100 +type('integer') // Integer only + +// Arrays and objects +type('string[]') // Array of strings +type({ name: 'string' }) // Object shape + +// Optional +type('string?') // string | undefined +type('string|null') // string | null +``` + +## Validation in Agents + +### Complete Example + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ + query: z.string().min(1), + options: z.object({ + limit: z.number().default(10), + includeMetadata: z.boolean().default(false), + }).optional(), + }), + output: z.object({ + results: z.array(z.object({ + id: z.string(), + title: z.string(), + score: z.number().min(0).max(1), + })), + count: z.number(), + }), + }, + handler: async (ctx, input) => { + // Input validated before handler runs + ctx.logger.info('Processing query', { query: input.query }); + + // Process the query... + const results = [ + { id: '1', title: 'Result 1', score: 0.95 }, + { id: '2', title: 'Result 2', score: 0.87 }, + ]; + + // Output validated before returning + return { + results, + count: results.length, + }; + }, +}); + +export default agent; +``` + +### Validation Behavior + +**Input validation:** +- Runs automatically before handler execution +- If validation fails, an error is thrown and the handler is not called +- Error includes detailed information about what failed + +**Output validation:** +- Runs automatically after handler execution +- If validation fails, an error is thrown before returning to the caller +- Ensures consistent output shape + +## Validation in Routes + +Schemas can be used in route handlers with the `zValidator` middleware. + +### Using zValidator + +```typescript +import { createRouter } from '@agentuity/runtime'; +import { zValidator } from '@hono/zod-validator'; +import agent from './agent'; + +const router = createRouter(); + +router.post('/', + zValidator('json', agent.inputSchema!), + async (c) => { + const data = c.req.valid('json'); // Type-safe, validated data + const result = await c.agent.myAgent.run(data); + return c.json(result); + } +); + +export default router; +``` + +### Route vs Agent Validation + +**Route validation** (with zValidator): +- Validates HTTP request body before reaching your handler +- Returns 400 error if validation fails +- Useful for public-facing endpoints + +**Agent validation** (with schema): +- Validates input when agent is called (from routes or other agents) +- Validates output before returning +- Ensures consistency across all callers + +Both can be used together for defense in depth. + +## Error Handling + +### Validation Failures + +When validation fails, an error is thrown with detailed information: + +```typescript +try { + await ctx.agent.userAgent.run({ + email: 'invalid-email', // Invalid format + age: -5, // Invalid range + }); +} catch (error) { + // Error message includes: + // - Which field failed validation + // - What the validation rule was + // - What the actual value was + console.error('Validation error:', error.message); +} +``` + +### Input Validation Errors + +If input validation fails, the handler never runs: + +```typescript +const agent = createAgent({ + schema: { + input: z.object({ age: z.number().min(0) }), + }, + handler: async (ctx, input) => { + // This never runs if age is negative + return { processed: true }; + }, +}); + +// This throws a validation error: +await ctx.agent.myAgent.run({ age: -1 }); +``` + +### Output Validation Errors + +If output validation fails, the error is thrown before returning: + +```typescript +const agent = createAgent({ + schema: { + output: z.object({ status: z.enum(['ok', 'error']) }), + }, + handler: async (ctx, input) => { + return { status: 'invalid' }; // Validation error! + }, +}); +``` + +## Advanced Patterns + +### Optional Fields + +Use `.optional()` for fields that may be omitted: + +```typescript +z.object({ + required: z.string(), + optional: z.string().optional(), + withDefault: z.number().default(10), +}) + +// Valid inputs: +{ required: 'value' } +{ required: 'value', optional: 'text' } +{ required: 'value', optional: 'text', withDefault: 20 } +``` + +### Nested Objects + +Define complex nested structures: + +```typescript +z.object({ + user: z.object({ + profile: z.object({ + name: z.string(), + avatar: z.string().url().optional(), + }), + settings: z.object({ + theme: z.enum(['light', 'dark']), + notifications: z.boolean(), + }), + }), +}) +``` + +### Arrays and Unions + +Validate arrays and multiple types: + +```typescript +z.object({ + tags: z.array(z.string()), // Array of strings + ids: z.array(z.number()).min(1).max(10), // 1-10 numbers + status: z.union([ // Multiple types + z.literal('active'), + z.literal('inactive'), + z.literal('pending') + ]), + value: z.string().or(z.number()), // String or number +}) +``` + +### Transformations + +Transform data during validation: + +```typescript +z.object({ + email: z.string().email().toLowerCase(), // Convert to lowercase + name: z.string().trim(), // Trim whitespace + age: z.string().transform(val => parseInt(val)), // Parse to number +}) +``` + +### Custom Validation + +Add custom validation logic: + +```typescript +z.object({ + password: z.string() + .min(8) + .refine(val => /[A-Z]/.test(val), { + message: 'Password must contain uppercase letter' + }), + + startDate: z.date(), + endDate: z.date(), +}).refine(data => data.endDate > data.startDate, { + message: 'End date must be after start date' +}); +``` + +## Best Practices + +### Always Validate Input + +Validate input for all agents to prevent runtime errors: + +```typescript +// Good - validated input +const agent = createAgent({ + schema: { + input: z.object({ + email: z.string().email(), + age: z.number().min(0) + }) + }, + handler: async (ctx, input) => { + // input is guaranteed valid + }, +}); +``` + +### Use Schemas as Documentation + +Schemas serve as clear documentation of your agent's API: + +```typescript +schema: { + input: z.object({ + // Query string to search for + query: z.string().min(1).max(500), + + // Optional filters + filters: z.object({ + category: z.enum(['tech', 'business', 'sports']), + dateRange: z.object({ + from: z.date(), + to: z.date(), + }).optional(), + }).optional(), + + // Maximum results (1-100) + limit: z.number().min(1).max(100).default(10), + }), +} +``` + +### Choose the Right Library + +**Zod** - Best for most projects: +- Most popular with excellent community support +- Great TypeScript integration +- Comprehensive validation features + +**Valibot** - Best for bundle size: +- Smaller bundle than Zod +- Similar API and features +- Good for client-side applications + +**ArkType** - Best for TypeScript-first teams: +- Concise type-first syntax +- Strong TypeScript inference +- Good for complex type scenarios + +### Validate Output for Consistency + +Define output schemas to ensure consistent responses: + +```typescript +schema: { + output: z.object({ + success: z.boolean(), + data: z.any().optional(), + error: z.string().optional(), + }) +} +``` + +This ensures all code paths return the expected shape. + +### Use Strict Mode + +Enable strict mode to catch common mistakes: + +```typescript +z.object({ + name: z.string() +}).strict() // Rejects unknown keys +``` + +This prevents accidentally passing extra fields that might indicate bugs. diff --git a/content/v1/Guides/sessions-threads.mdx b/content/v1/Guides/sessions-threads.mdx new file mode 100644 index 00000000..d1610b1c --- /dev/null +++ b/content/v1/Guides/sessions-threads.mdx @@ -0,0 +1,880 @@ +--- +title: Sessions & Threads +description: Stateful context management for conversational agents +--- + +Sessions and threads provide stateful context management at three different scopes: request-level for temporary data, thread-level for conversation context lasting up to 1 hour, and session-level for user data spanning multiple conversations. Use request state (`ctx.state`) for timing and calculations, thread state (`ctx.thread.state`) for chatbot memory, and session state (`ctx.session.state`) for user preferences and cross-conversation tracking. + +For event-based lifecycle management of sessions and threads, see the [Events Guide](/Guides/events). + +## Understanding the Three Scopes + +The SDK provides three distinct state scopes, each with different lifetimes and use cases: + +- **Request state** (`ctx.state`) - Temporary data within a single request, cleared after response +- **Thread state** (`ctx.thread.state`) - Conversation context across multiple requests, expires after 1 hour +- **Session state** (`ctx.session.state`) - User-level data spanning multiple threads and conversations + +### Three State Scopes in Action + +This example demonstrates all three scopes and when each is used: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ message: z.string() }), + output: z.object({ + response: z.string(), + requestTime: z.number(), + conversationLength: z.number(), + totalUserRequests: z.number() + }) + }, + handler: async (ctx, input) => { + // REQUEST STATE: Temporary data for this request only + ctx.state.set('requestStart', Date.now()); + + // THREAD STATE: Conversation history (persists up to 1 hour) + const messages = (ctx.thread.state.get('messages') as string[]) || []; + messages.push(input.message); + ctx.thread.state.set('messages', messages); + + // SESSION STATE: User-level data (persists across threads) + const totalRequests = (ctx.session.state.get('totalRequests') as number) || 0; + ctx.session.state.set('totalRequests', totalRequests + 1); + ctx.session.state.set('lastMessage', input.message); + + // Request state is used for response calculation + const requestTime = Date.now() - (ctx.state.get('requestStart') as number); + + return { + response: `Received: ${input.message}`, + requestTime, + conversationLength: messages.length, + totalUserRequests: totalRequests + 1 + }; + // REQUEST STATE cleared after this return + // THREAD STATE persists for up to 1 hour + // SESSION STATE persists indefinitely (in memory) + } +}); + +export default agent; +``` + +**Key Points:** +- Request state exists only during handler execution +- Thread state maintains conversation context +- Session state tracks user-level metrics across conversations + +### State Scope Comparison + +| Scope | Lifetime | Cleared When | Use Case | Access | +|-------|----------|--------------|----------|--------| +| Request | Single request | After response sent | Timing, temp calculations | `ctx.state` | +| Thread | Up to 1 hour | Thread expiration or destroy | Conversation history | `ctx.thread.state` | +| Session | Spans threads | In-memory (provider dependent) | User preferences | `ctx.session.state` | + +## Thread Management + +Threads represent conversation contexts with a 1-hour lifetime. Each thread is identified by a unique thread ID stored in a cookie, enabling conversation continuity across multiple requests. + +### Thread Lifecycle + +**Creation:** +- Threads are created automatically on the first request from a client +- Thread ID (format: `thrd_`) is stored in the `atid` cookie +- Cookie enables thread restoration on subsequent requests + +**Restoration:** +- If the `atid` cookie is present, the existing thread is restored +- Thread state is maintained across requests from the same client + +**Expiration:** +- Threads expire after 1 hour (3600000ms) of inactivity +- Expired threads are automatically cleaned up every 60 seconds +- `destroyed` event fires when thread expires + +**Manual Destroy:** +- Call `await ctx.thread.destroy()` to reset the conversation +- Useful for starting new conversations or clearing context + +### Conversation Context with Threads + +Load conversation from KV storage and cache in thread state for fast access: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ + message: z.string(), + reset: z.boolean().optional() + }), + output: z.object({ + response: z.string(), + messageCount: z.number(), + source: z.enum(['kv', 'thread-state', 'new']) + }) + }, + handler: async (ctx, input) => { + // Reset conversation if requested + if (input.reset) { + await ctx.thread.destroy(); + } + + const conversationKey = `conversation_${ctx.thread.id}`; + let source: 'kv' | 'thread-state' | 'new' = 'new'; + + // Load conversation from KV on first access + if (!ctx.thread.state.has('messages')) { + const result = await ctx.kv.get('conversations', conversationKey); + + if (result.exists) { + const saved = await result.data.json(); + ctx.thread.state.set('messages', saved.messages); + source = 'kv'; + ctx.logger.info('Loaded conversation from KV', { + threadId: ctx.thread.id, + messageCount: saved.messages.length + }); + } else { + ctx.thread.state.set('messages', []); + } + + // Register save handler when thread is destroyed + ctx.thread.addEventListener('destroyed', async (eventName, thread) => { + const messages = thread.state.get('messages') as string[]; + + if (messages && messages.length > 0) { + await ctx.kv.set('conversations', conversationKey, { + threadId: thread.id, + messages, + savedAt: new Date().toISOString() + }, { + ttl: 86400 // Keep for 24 hours + }); + + ctx.logger.info('Saved conversation to KV', { + threadId: thread.id, + messageCount: messages.length + }); + } + }); + } else { + source = 'thread-state'; + } + + // Add message to thread state (fast access) + const messages = ctx.thread.state.get('messages') as string[]; + messages.push(input.message); + ctx.thread.state.set('messages', messages); + + return { + response: `Stored message ${messages.length}`, + messageCount: messages.length, + source + }; + } +}); + +export default agent; +``` + +**Key Points:** +- Load from KV on first access, cache in thread.state +- Thread state provides fast access during thread lifetime +- Save to KV when thread is destroyed for persistence +- Source field shows where data came from (KV, thread-state, or new) + +## Session Management + +Sessions represent individual request executions with unique session IDs. While threads track conversations, sessions track user-level data that spans multiple threads. + +### Session Lifecycle + +**Creation:** +- A new session is created for each request +- Each session has a unique `sessionId` accessible via `ctx.sessionId` +- Sessions belong to a thread: `ctx.session.thread` + +**State:** +- `ctx.session.state` persists across threads for the same user +- Use for user preferences, settings, cross-conversation data +- State stored in memory (persistence depends on SessionProvider) + +**Completion:** +- Sessions complete at the end of each request +- `session.completed` event fires on completion +- Use event for persisting data to storage + +## Thread Expiration & Cleanup + +Threads automatically expire after 1 hour of inactivity. The cleanup process runs every 60 seconds to remove expired threads. + +### Thread Cleanup on Expiration + +Clean up resources when threads are destroyed (either by expiration or manual destroy): + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const agent = createAgent({ + handler: async (ctx, input) => { + // Register cleanup handler once per thread + if (!ctx.thread.state.has('cleanupRegistered')) { + ctx.thread.addEventListener('destroyed', async (eventName, thread) => { + ctx.logger.info('Thread destroyed - cleaning up', { + threadId: thread.id, + messageCount: thread.state.get('messageCount') || 0, + conversationDuration: Date.now() - (thread.state.get('startTime') as number || Date.now()) + }); + + // Save conversation summary to KV before cleanup + const messages = thread.state.get('messages') as string[] || []; + if (messages.length > 0) { + await ctx.kv.set('conversation-summaries', thread.id, { + messageCount: messages.length, + lastMessages: messages.slice(-5), + endedAt: new Date().toISOString() + }, { + ttl: 86400 // Keep summary for 24 hours + }); + } + + // Clear thread state + thread.state.clear(); + }); + + ctx.thread.state.set('cleanupRegistered', true); + ctx.thread.state.set('startTime', Date.now()); + } + + // Track messages + const messageCount = (ctx.thread.state.get('messageCount') as number) || 0; + ctx.thread.state.set('messageCount', messageCount + 1); + + const messages = (ctx.thread.state.get('messages') as string[]) || []; + messages.push(JSON.stringify(input)); + ctx.thread.state.set('messages', messages); + + return { processed: true, messageCount: messageCount + 1 }; + } +}); + +export default agent; +``` + +**Key Points:** +- Threads expire after 1 hour of inactivity +- `destroyed` event fires on expiration or manual destroy +- Use event for cleanup and saving important data +- Register event listener once per thread + +## Common Patterns + +### Chatbot with Conversation Memory + +Production-ready chatbot with LLM integration and conversation persistence: + + +This example requires the Vercel AI SDK. Install with: +```bash +npm install ai @ai-sdk/openai +``` + + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; +import { streamText } from 'ai'; +import { openai } from '@ai-sdk/openai'; +import type { CoreMessage } from 'ai'; + +const agent = createAgent({ + schema: { + input: z.object({ + message: z.string() + }), + stream: true + }, + handler: async (ctx, input) => { + const conversationKey = `chat_${ctx.thread.id}`; + + // Load conversation history from KV + let messages: CoreMessage[] = []; + + try { + const result = await ctx.kv.get('conversations', conversationKey); + + if (result.exists) { + messages = await result.data.json() as CoreMessage[]; + ctx.logger.info('Loaded conversation from KV', { + threadId: ctx.thread.id, + messageCount: messages.length + }); + } + } catch (error) { + ctx.logger.error('Error loading conversation history', { error }); + } + + // Add user message to conversation + messages.push({ + role: 'user', + content: input.message + }); + + // Stream LLM response with conversation context + const result = streamText({ + model: openai('gpt-4o-mini'), + system: 'You are a helpful assistant. Keep responses concise.', + messages + }); + + // Save conversation after stream completes (non-blocking) + ctx.waitUntil(async () => { + try { + const fullText = await result.text; + + // Add assistant response to messages + messages.push({ + role: 'assistant', + content: fullText + }); + + // Keep last 20 messages (10 turns) to manage state size + const recentMessages = messages.slice(-20); + + // Save to KV storage + await ctx.kv.set('conversations', conversationKey, recentMessages, { + ttl: 86400 // 24 hours + }); + + ctx.logger.info('Saved conversation to KV', { + threadId: ctx.thread.id, + messageCount: recentMessages.length + }); + } catch (error) { + ctx.logger.error('Error saving conversation history', { error }); + } + }); + + // Return stream to client + return result.textStream; + } +}); + +export default agent; +``` + +**Key Points:** +- LLM integration with conversation context +- Load conversation from KV using thread ID as key +- Stream response to client for better UX +- Use `waitUntil()` to save asynchronously after stream completes +- Limit conversation to last 20 messages to manage memory +- Error handling for KV operations + +### User Preferences with Persistence + +Load, cache, and persist user preferences using session state and KV storage: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const PreferencesSchema = z.object({ + language: z.string(), + theme: z.enum(['light', 'dark']), + notifications: z.boolean(), + timezone: z.string() +}); + +const agent = createAgent({ + schema: { + input: z.object({ + userId: z.string(), + updatePreferences: PreferencesSchema.partial().optional() + }), + output: z.object({ + preferences: PreferencesSchema, + source: z.enum(['cache', 'storage', 'default']) + }) + }, + handler: async (ctx, input) => { + const userId = input.userId; + let source: 'cache' | 'storage' | 'default' = 'default'; + + // Check session state cache first + let preferences = ctx.session.state.get(`preferences_${userId}`) as z.infer | undefined; + + if (preferences) { + source = 'cache'; + } else { + // Load from KV storage + const result = await ctx.kv.get('user-preferences', userId); + + if (result.exists) { + preferences = await result.data.json(); + source = 'storage'; + } else { + // Use defaults + preferences = { + language: 'en', + theme: 'light', + notifications: true, + timezone: 'UTC' + }; + } + + // Cache in session state + ctx.session.state.set(`preferences_${userId}`, preferences); + } + + // Update if requested + if (input.updatePreferences) { + preferences = { ...preferences, ...input.updatePreferences }; + ctx.session.state.set(`preferences_${userId}`, preferences); + + // Persist to KV storage + await ctx.kv.set('user-preferences', userId, preferences, { + ttl: 2592000 // 30 days + }); + } + + return { preferences, source }; + } +}); + +export default agent; +``` + +**Key Points:** +- Load from KV storage first, cache in session state +- Session state provides fast access across requests +- Persist updates back to KV for durability +- `source` field shows data origin (cache, storage, or default) + +### Multi-Turn Workflow State + +Track workflow progress across multiple requests: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const WorkflowStateSchema = z.object({ + step: z.enum(['start', 'collectName', 'collectEmail', 'confirm', 'complete']), + data: z.object({ + name: z.string().optional(), + email: z.string().optional() + }) +}); + +const agent = createAgent({ + schema: { + input: z.object({ + value: z.string().optional(), + reset: z.boolean().optional() + }), + output: z.object({ + message: z.string(), + step: z.string(), + complete: z.boolean() + }) + }, + handler: async (ctx, input) => { + // Reset workflow if requested + if (input.reset) { + ctx.thread.state.delete('workflowState'); + } + + // Initialize workflow + let workflowState = ctx.thread.state.get('workflowState') as z.infer | undefined; + + if (!workflowState) { + workflowState = { + step: 'start', + data: {} + }; + } + + let message = ''; + let complete = false; + + // Process based on current step + switch (workflowState.step) { + case 'start': + message = 'Welcome! Please provide your name.'; + workflowState.step = 'collectName'; + break; + + case 'collectName': + if (input.value) { + workflowState.data.name = input.value; + workflowState.step = 'collectEmail'; + message = `Hello ${input.value}! Please provide your email.`; + } else { + message = 'Please provide your name.'; + } + break; + + case 'collectEmail': + if (input.value) { + workflowState.data.email = input.value; + workflowState.step = 'confirm'; + message = `Please confirm: Name: ${workflowState.data.name}, Email: ${input.value}. Reply 'yes' to confirm.`; + } else { + message = 'Please provide your email.'; + } + break; + + case 'confirm': + if (input.value?.toLowerCase() === 'yes') { + workflowState.step = 'complete'; + message = 'Registration complete!'; + complete = true; + + // Save to storage + await ctx.kv.set('registrations', workflowState.data.email!, workflowState.data, { + ttl: 86400 + }); + } else { + message = 'Please reply "yes" to confirm or start over.'; + } + break; + + case 'complete': + message = 'Workflow already complete. Use reset=true to start over.'; + complete = true; + break; + } + + // Save workflow state + ctx.thread.state.set('workflowState', workflowState); + + return { + message, + step: workflowState.step, + complete + }; + } +}); + +export default agent; +``` + +### Session-Based Rate Limiting + +Implement rate limiting per session: + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const agent = createAgent({ + handler: async (ctx, input) => { + const requestLimit = 10; + const windowMs = 60000; // 1 minute + + // Track requests in session state + const requestLog = (ctx.session.state.get('requestLog') as Array) || []; + const now = Date.now(); + + // Remove requests outside the time window + const recentRequests = requestLog.filter(timestamp => now - timestamp < windowMs); + + // Check rate limit + if (recentRequests.length >= requestLimit) { + const oldestRequest = Math.min(...recentRequests); + const resetIn = windowMs - (now - oldestRequest); + + ctx.logger.warn('Rate limit exceeded', { + sessionId: ctx.sessionId, + requestCount: recentRequests.length, + resetInMs: resetIn + }); + + return { + error: 'Rate limit exceeded', + resetInSeconds: Math.ceil(resetIn / 1000), + requestCount: recentRequests.length, + limit: requestLimit + }; + } + + // Add current request + recentRequests.push(now); + ctx.session.state.set('requestLog', recentRequests); + + return { + processed: true, + requestCount: recentRequests.length, + remainingRequests: requestLimit - recentRequests.length + }; + } +}); + +export default agent; +``` + +## State Persistence + +In-memory state is ephemeral and lost on server restarts. For durable data, use KV storage in combination with thread and session state. + +**Pattern:** +1. Load data from KV storage +2. Cache in thread/session state for fast access +3. Update state during request processing +4. Save back to KV on thread destroy or session complete + +### Session-Level Data Persistence + +Persist user profile data across sessions: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const UserProfileSchema = z.object({ + userId: z.string(), + name: z.string(), + preferences: z.object({ + language: z.string(), + timezone: z.string() + }), + lastActive: z.string() +}); + +const agent = createAgent({ + schema: { + input: z.object({ + userId: z.string(), + updateProfile: UserProfileSchema.partial().optional() + }), + output: z.object({ + profile: UserProfileSchema + }) + }, + handler: async (ctx, input) => { + const userId = input.userId; + + // Load profile from session state cache + let profile = ctx.session.state.get(`profile_${userId}`) as z.infer | undefined; + + // If not in cache, load from KV + if (!profile) { + const result = await ctx.kv.get('user-profiles', userId); + + if (result.exists) { + profile = await result.data.json(); + } else { + // Create default profile + profile = { + userId, + name: 'Guest', + preferences: { + language: 'en', + timezone: 'UTC' + }, + lastActive: new Date().toISOString() + }; + } + + // Cache in session state + ctx.session.state.set(`profile_${userId}`, profile); + + // Register session completion handler to save on exit + if (!ctx.session.state.has('saveHandlerRegistered')) { + ctx.session.addEventListener('completed', async (eventName, session) => { + const profileToSave = session.state.get(`profile_${userId}`) as typeof profile; + + if (profileToSave) { + await ctx.kv.set('user-profiles', userId, profileToSave, { + ttl: 2592000 // 30 days + }); + + ctx.logger.info('Saved user profile', { userId }); + } + }); + + ctx.session.state.set('saveHandlerRegistered', true); + } + } + + // Update profile if requested + if (input.updateProfile) { + profile = { ...profile, ...input.updateProfile }; + profile.lastActive = new Date().toISOString(); + ctx.session.state.set(`profile_${userId}`, profile); + } + + return { profile }; + } +}); + +export default agent; +``` + +## Best Practices + + +**State Management Guidelines** + +- **Use the right scope** - Request state for temp data, thread state for conversations, session state for user data +- **Keep state size manageable** - Limit conversation history, avoid large objects in memory +- **Persist important data** - Use KV storage for data that must survive restarts +- **Clean up resources** - Register cleanup handlers in `destroyed` and `completed` events +- **Cache strategically** - Load from KV once, cache in state, save on completion +- **Don't rely on state for critical data** - In-memory state can be lost on deployment or restart +- **Use thread.destroy() for resets** - Don't use it for cleanup (use events instead) +- **Session IDs are per-request** - Use for analytics and tracing, not as user identifiers +- **Thread IDs span requests** - Use for conversation grouping and context + + +### State Size Management + +Keep conversation history bounded to avoid memory issues: + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const agent = createAgent({ + handler: async (ctx, input) => { + const maxMessages = 50; // Keep last 50 messages in memory + const archiveThreshold = 40; // Archive when reaching 40 messages + + // Get current messages from thread state + const messages = (ctx.thread.state.get('messages') as string[]) || []; + + // Add new message + messages.push(JSON.stringify(input)); + + // Archive old messages to KV when threshold is reached + if (messages.length >= archiveThreshold) { + const toArchive = messages.slice(0, messages.length - maxMessages); + + if (toArchive.length > 0) { + // Archive to KV storage + const archiveKey = `archive_${ctx.thread.id}_${Date.now()}`; + await ctx.kv.set('message-archives', archiveKey, { + threadId: ctx.thread.id, + messages: toArchive, + archivedAt: new Date().toISOString() + }, { + ttl: 604800 // Keep for 7 days + }); + + ctx.logger.info('Archived old messages', { + threadId: ctx.thread.id, + archivedCount: toArchive.length + }); + } + + // Keep only recent messages in state + const recentMessages = messages.slice(-maxMessages); + ctx.thread.state.set('messages', recentMessages); + } else { + ctx.thread.state.set('messages', messages); + } + + return { + processed: true, + messagesInMemory: messages.length + }; + } +}); + +export default agent; +``` + +**Key Points:** +- Set maximum size limits for state collections +- Archive old data to KV storage +- Keep recent data in state for fast access +- Prevent unbounded memory growth + +## Understanding Thread vs Session IDs + +Thread IDs and session IDs serve different purposes in the SDK: + +**Thread ID (`ctx.thread.id`):** +- Represents a conversation or interaction sequence +- Format: `thrd_<32-char-hex>` +- Stored in cookie `atid` for client persistence +- Same thread ID across multiple requests (up to 1 hour) +- Use for grouping related requests into conversations + +**Session ID (`ctx.sessionId`):** +- Represents a single request execution +- Format: `sess_<32-char-hex>` (or similar) +- Unique for each request +- New session ID even within the same thread +- Use for request tracing, logging, and analytics + +### Tracking with IDs + +Demonstrate the relationship between thread and session IDs: + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const agent = createAgent({ + handler: async (ctx, input) => { + // Track unique sessions per thread + const sessionsSeen = (ctx.thread.state.get('sessionsSeen') as Set) || new Set(); + sessionsSeen.add(ctx.sessionId); + ctx.thread.state.set('sessionsSeen', sessionsSeen); + + // Log both IDs for correlation + ctx.logger.info('Request tracking', { + threadId: ctx.thread.id, // Same across conversation + sessionId: ctx.sessionId, // Unique per request + requestNumber: sessionsSeen.size, + threadDuration: Date.now() - (ctx.thread.state.get('threadStartTime') as number || Date.now()) + }); + + // Initialize thread tracking + if (!ctx.thread.state.has('threadStartTime')) { + ctx.thread.state.set('threadStartTime', Date.now()); + } + + return { + threadId: ctx.thread.id, + sessionId: ctx.sessionId, + requestsInThread: sessionsSeen.size, + explanation: { + threadId: 'Groups related requests into a conversation (up to 1 hour)', + sessionId: 'Uniquely identifies this specific request' + } + }; + } +}); + +export default agent; +``` + +**Expected Output:** +```javascript +// Request 1: +{ threadId: 'thrd_abc123...', sessionId: 'sess_xyz789...', requestsInThread: 1 } + +// Request 2 (same conversation): +{ threadId: 'thrd_abc123...', sessionId: 'sess_def456...', requestsInThread: 2 } +// Note: Same threadId, different sessionId + +// Request 3 (after 1 hour, new thread): +{ threadId: 'thrd_ghi789...', sessionId: 'sess_jkl012...', requestsInThread: 1 } +// Note: New threadId and sessionId +``` + +**Key Points:** +- Thread ID groups requests into conversations +- Session ID uniquely identifies each request +- Use thread ID for conversation context +- Use session ID for request tracing and analytics +- Both IDs are essential for different tracking purposes diff --git a/content/v1/Guides/subagents.mdx b/content/v1/Guides/subagents.mdx new file mode 100644 index 00000000..a0111e34 --- /dev/null +++ b/content/v1/Guides/subagents.mdx @@ -0,0 +1,747 @@ +--- +title: Subagents +description: Organize related agents into parent-child hierarchies +--- + +Subagents organize related agents into parent-child hierarchies with one level of nesting. Use subagents to group related functionality under a common parent, share validation logic, and create hierarchical API structures. Each subagent maintains its own handler and routes while accessing the parent agent via `ctx.parent`. + +For routing patterns and middleware configuration, see the [Routing & Triggers Guide](/Guides/routing-triggers). For general agent patterns, see [Core Concepts](/Introduction/core-concepts). + +## Understanding Subagents + +Subagents are regular agents nested within a parent agent's directory. The file structure determines the relationship: agents in subdirectories become subagents automatically. This enables logical grouping of related operations while maintaining clear separation of concerns. + +**File Structure:** +``` +team/ # Parent agent +├── agent.ts # Parent logic +├── route.ts # Parent routes +├── members/ # Subagent: member operations +│ ├── agent.ts +│ └── route.ts +└── tasks/ # Subagent: task operations + ├── agent.ts + └── route.ts +``` + +**Resulting Structure:** +- Parent agent name: `team` +- Subagent names: `team.members`, `team.tasks` +- Parent routes: `/agent/team` +- Subagent routes: `/agent/team/members`, `/agent/team/tasks` + +### When to Use Subagents + +Use subagents when you have: + +- **Related operations in the same domain** - User profile, settings, and notifications belong together +- **Shared validation or authentication** - Parent validates permissions once for all subagents +- **Natural parent-child relationship** - Product catalog contains inventory, pricing, and reviews +- **Hierarchical API structure** - Routes reflect domain hierarchy (`/team/members`, `/team/tasks`) + +### When to Use Separate Agents + +Use separate agents when you have: + +- **Independent domains** - User management and payment processing are unrelated +- **Different ownership** - Multiple teams maintain different parts of the system +- **No shared context** - Agents don't need common validation or state +- **Flexible composition** - Agent may be called from many different contexts + +### Comparison Table + +| Feature | Subagents | Separate Agents | +|---------|-----------|-----------------| +| **File structure** | Nested directories | Flat structure | +| **Access pattern** | `ctx.agent.parent.child.run()` | `ctx.agent.agentName.run()` | +| **Routes** | Inherit parent path | Independent paths | +| **Shared logic** | Parent provides context | Shared via middleware | +| **Parent access** | `ctx.parent` available | N/A | +| **Coupling** | Moderate (shares parent) | Low (independent) | +| **Use case** | Related operations | Independent operations | + +## Creating Parent Agents + +Parent agents are regular agents with subdirectories containing subagents. The parent provides coordination logic, shared validation, and high-level operations. + +```typescript +// team/agent.ts +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ + action: z.enum(['info', 'stats']) + }), + output: z.object({ + message: z.string(), + memberCount: z.number().optional(), + taskCount: z.number().optional() + }) + }, + handler: async (ctx, input) => { + if (input.action === 'info') { + return { message: 'Team management system' }; + } + + // Coordinate between subagents + const [members, tasks] = await Promise.all([ + ctx.agent.team.members.run({ action: 'count' }), + ctx.agent.team.tasks.run({ action: 'count' }) + ]); + + return { + message: 'Team statistics', + memberCount: members.count, + taskCount: tasks.count + }; + } +}); + +export default agent; + +// Routes: team/route.ts - standard routing applies +// GET /agent/team → calls agent with default input +// POST /agent/team → calls agent with request body +``` + +**Key Points:** +- Parent is a standard agent created with `createAgent()` +- Can call subagents via `ctx.agent.parent.child.run()` +- Provides high-level coordination between subagents +- Routes follow standard patterns (see [Routing & Triggers Guide](/Guides/routing-triggers)) + +## Creating Subagents + +Subagents are created by placing agent.ts and route.ts files in a subdirectory of the parent agent. The directory structure is automatically detected during bundling. + +**File Structure Requirements:** +- **Location**: Must be in a subdirectory of the parent agent +- **Files**: Both `agent.ts` and `route.ts` required +- **Depth**: One level only (no grandchildren) +- **Detection**: Automatically detected by the bundler + +**Naming Convention:** +- Agent name: `parent.child` (e.g., `team.members`) +- Property access: `ctx.agent.team.members` (nested object) +- Current agent name: `ctx.agentName` returns `"team.members"` + +### Basic Subagent + +Create a subagent for member management: + +```typescript +// team/members/agent.ts +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ + action: z.enum(['list', 'add', 'remove', 'count']), + name: z.string().optional() + }), + output: z.object({ + members: z.array(z.string()), + count: z.number().optional() + }) + }, + handler: async (ctx, input) => { + const result = await ctx.kv.get('team-data', 'members'); + let members: string[] = result.exists && result.data ? result.data : []; + + if (input.action === 'add' && input.name) { + members.push(input.name); + await ctx.kv.set('team-data', 'members', members); + } else if (input.action === 'remove' && input.name) { + members = members.filter(m => m !== input.name); + await ctx.kv.set('team-data', 'members', members); + } else if (input.action === 'count') { + return { members, count: members.length }; + } + + return { members }; + } +}); + +export default agent; +``` + +**Key Points:** +- Same `createAgent()` syntax as regular agents +- Agent name automatically becomes `team.members` +- Access via `ctx.agentName` returns `"team.members"` +- Full schema validation and type inference + + +**Nesting Depth:** Only one level of nesting is supported. Grandchildren (`team/members/admins/agent.ts`) are not supported. If you need deeper organization, create separate subagents at the same level (e.g., `team/members/` and `team/memberAdmins/`). + + +### Subagent Routes + +Routes inherit the parent's path and follow standard patterns: + +```typescript +// team/members/route.ts +import { createRouter } from '@agentuity/runtime'; +import { zValidator } from '@hono/zod-validator'; +import { z } from 'zod'; + +const router = createRouter(); + +// GET /agent/team/members +router.get('/', async (c) => { + const result = await c.agent.team.members.run({ action: 'list' }); + return c.json(result); +}); + +// POST /agent/team/members/add +router.post('/add', zValidator('json', z.object({ name: z.string() })), async (c) => { + const data = c.req.valid('json'); + const result = await c.agent.team.members.run({ action: 'add', name: data.name }); + return c.json(result); +}); + +export default router; +``` + +**Key Points:** +- Routes inherit parent path: `/agent/team/members` +- Access subagent via `c.agent.team.members.run()` +- Standard Hono routing patterns apply +- See [Routing & Triggers Guide](/Guides/routing-triggers) for complete patterns + +## Accessing Subagents + +Subagents are accessed via nested properties on `ctx.agent` or `c.agent`. Type inference provides autocomplete and compile-time validation when schemas are defined. + +```typescript +// From routes or other agents +const router = createRouter(); + +router.get('/summary', async (c) => { + // Parallel execution + const [members, tasks] = await Promise.all([ + c.agent.team.members.run({ action: 'list' }), + c.agent.team.tasks.run({ action: 'list' }) + ]); + + return c.json({ + memberCount: members.members.length, + taskCount: tasks.tasks.length + }); +}); + +// From another agent +const agent = createAgent({ + handler: async (ctx, input) => { + const members = await ctx.agent.team.members.run({ action: 'count' }); + return { count: members.count }; + } +}); +``` + +**Key Points:** +- Nested property access: `ctx.agent.parent.child.run()` +- Works from routes and other agents +- Supports parallel execution with `Promise.all()` +- Type-safe when both agents have schemas + +**Accessing Sibling Subagents:** + +From within a subagent, access siblings via the full path (not via `ctx.sibling`): + +```typescript +// From team/members/agent.ts +const tasksData = await ctx.agent.team.tasks.run({ action: 'list' }); // ✓ +``` + + +**Type Safety:** When agents define schemas with Zod/Valibot/ArkType, TypeScript provides full type inference for inputs and outputs, including autocomplete and compile-time validation. + + +## Parent Context Access + +Subagents access their parent agent via `ctx.parent`. This enables validation, shared configuration, and coordination patterns while maintaining separation of concerns. + +### Basic Usage + +The `ctx.parent` property is available only in subagent handlers: + +```typescript +// team/members/agent.ts +const agent = createAgent({ + handler: async (ctx, input) => { + let parentInfo: string | undefined; + + // Check if parent is available + if (ctx.parent) { + const parentData = await ctx.parent.run({ action: 'info' }); + parentInfo = parentData.message; + + ctx.logger.info('Retrieved parent context', { + parentMessage: parentInfo, + currentAgent: ctx.agentName // "team.members" + }); + } + + return { result: 'Processed', parentInfo }; + } +}); +``` + +**Key Points:** +- `ctx.parent` available only in subagents (undefined in parent) +- Call parent via `ctx.parent.run(input)` +- Parent execution is synchronous (blocks subagent) +- Always check `if (ctx.parent)` for type safety + +### Shared Validation Pattern + +Use parent for centralized validation logic: + +```typescript +// team/agent.ts - Parent validates permissions +const teamAgent = createAgent({ + schema: { + input: z.object({ + userId: z.string(), + teamId: z.string(), + requiredRole: z.enum(['member', 'admin', 'owner']).optional() + }), + output: z.object({ + authorized: z.boolean(), + userRole: z.string() + }) + }, + handler: async (ctx, input) => { + const result = await ctx.kv.get('team-memberships', `${input.teamId}:${input.userId}`); + + if (!result.exists) { + throw new Error('User is not a member of this team'); + } + + const membership = result.data as { role: string }; + + // Check role if required + if (input.requiredRole) { + const roleHierarchy = { member: 1, admin: 2, owner: 3 }; + const userLevel = roleHierarchy[membership.role as keyof typeof roleHierarchy] || 0; + const requiredLevel = roleHierarchy[input.requiredRole]; + + if (userLevel < requiredLevel) { + throw new Error(`Requires ${input.requiredRole} role`); + } + } + + return { authorized: true, userRole: membership.role }; + } +}); + +// team/members/agent.ts - Uses parent validation +const membersAgent = createAgent({ + handler: async (ctx, input) => { + // Validate with admin role requirement + if (ctx.parent) { + await ctx.parent.run({ + userId: input.userId, + teamId: input.teamId, + requiredRole: 'admin' + }); + } + + // Admin-only operation + const members = await ctx.kv.get('team-members', input.teamId); + return { members: members.data || [] }; + } +}); +``` + +**Key Points:** +- Parent provides centralized validation +- Subagents call parent before executing +- Validation errors propagate to caller +- Reduces duplication across subagents + +### When to Use Parent Access + +Use `ctx.parent` for: +- **Validation** - Check permissions before executing subagent logic +- **Shared configuration** - Retrieve settings managed by parent +- **Logging context** - Include parent information in logs + +Avoid `ctx.parent` for: +- **Core business logic** - Subagent should be mostly independent +- **Data operations** - Use storage directly, not via parent +- **Frequent calls** - Each call is synchronous and blocks execution +- **Circular calls** - Avoid parent calling child that calls parent back (creates infinite loop) + +## Route Organization + +Routes in subagent hierarchies follow the parent path structure. Middleware applied to parent routes cascades to all subagent routes. + +### Path Inheritance + +Subagent routes automatically inherit the parent path: + +``` +Parent: GET /agent/team + POST /agent/team + +Subagent: GET /agent/team/members + POST /agent/team/members/add + POST /agent/team/members/remove +``` + +### Middleware Cascade + +Middleware applied to parent routes cascades to all subagents: + +```typescript +// team/route.ts - Parent with authentication +const router = createRouter(); + +router.use('/*', async (c, next) => { + const token = c.req.header('Authorization'); + if (!token) return c.json({ error: 'Unauthorized' }, 401); + + c.set('userId', await getUserIdFromToken(token)); + await next(); +}); + +// All subagent routes inherit this auth middleware +``` + +**Key Points:** +- Parent middleware automatically applies to all subagent routes +- Subagents can add additional middleware +- Middleware executes in order: parent first, then subagent + + +For complete routing documentation including HTTP methods, middleware patterns, and specialized routes (WebSocket, SSE, cron), see the [Routing & Triggers Guide](/Guides/routing-triggers). + + +## Common Patterns + +### User Account Management + +Organize user-related operations under a parent user agent: + +``` +user/ +├── agent.ts # Core user operations +├── route.ts +├── profile/ # Profile management +│ ├── agent.ts +│ └── route.ts +├── settings/ # User settings +│ ├── agent.ts +│ └── route.ts +└── notifications/ # Notification preferences + ├── agent.ts + └── route.ts +``` + +**Parent agent handles core user logic:** + +```typescript +// user/agent.ts +const userAgent = createAgent({ + schema: { + input: z.object({ + userId: z.string(), + action: z.enum(['get', 'validate']) + }), + output: z.object({ + user: z.object({ + id: z.string(), + email: z.string(), + status: z.enum(['active', 'suspended']) + }).optional(), + valid: z.boolean().optional() + }) + }, + handler: async (ctx, input) => { + const result = await ctx.kv.get('users', input.userId); + if (!result.exists) throw new Error('User not found'); + + const user = result.data; + if (input.action === 'validate') { + return { valid: user.status === 'active' }; + } + + return { user }; + } +}); +``` + +**Subagent manages profile data:** + +```typescript +// user/profile/agent.ts +const profileAgent = createAgent({ + schema: { + input: z.object({ + userId: z.string(), + action: z.enum(['get', 'update']), + profile: z.object({ + displayName: z.string().optional(), + bio: z.string().optional(), + avatar: z.string().url().optional() + }).optional() + }), + output: z.object({ + profile: z.object({ + displayName: z.string(), + bio: z.string(), + avatar: z.string().url().optional() + }) + }) + }, + handler: async (ctx, input) => { + // Validate user via parent + if (ctx.parent) { + const validation = await ctx.parent.run({ + userId: input.userId, + action: 'validate' + }); + if (!validation.valid) { + throw new Error('User account is not active'); + } + } + + const result = await ctx.kv.get(`profile-${input.userId}`, 'user-profiles'); + let profile = result.exists ? result.data : { + displayName: 'New User', + bio: '' + }; + + if (input.action === 'update' && input.profile) { + profile = { ...profile, ...input.profile }; + await ctx.kv.set('user-profiles', `profile-${input.userId}`, profile); + } + + return { profile }; + } +}); +``` + +**Key Points:** +- Parent validates user status +- Subagents manage specific user data +- Clear separation: core vs. profile vs. settings vs. notifications +- Each subagent can be tested independently + +### Other Domain Patterns + +**E-commerce Product Management:** +``` +product/ +├── inventory/ # Stock management +├── pricing/ # Price operations +└── reviews/ # Customer reviews +``` +- Parent: Product validation and catalog management +- Subagents: Domain-specific operations (inventory, pricing, reviews) +- Use case: E-commerce platforms requiring organized product operations + +**Content Management:** +``` +content/ +├── drafts/ # Draft management +├── publishing/ # Publish workflow +└── media/ # Media attachments +``` +- Parent: Content workflow coordination +- Subagents: Stage-specific operations (drafting, publishing, media) +- Use case: CMS platforms with multi-step content workflows + +For complete working examples of these patterns, see the [Examples](/Examples) page. + +## Best Practices + +### Design Guidelines + +**Parent Coordinates, Children Execute:** +```typescript +// Good: Parent coordinates +const teamAgent = createAgent({ + handler: async (ctx, input) => { + const [members, tasks] = await Promise.all([ + ctx.agent.team.members.run({ action: 'count' }), + ctx.agent.team.tasks.run({ action: 'count' }) + ]); + return { memberCount: members.count, taskCount: tasks.count }; + } +}); + +// Bad: Parent implements domain logic +const teamAgent = createAgent({ + handler: async (ctx, input) => { + const members = await ctx.kv.get('members', 'list'); + return { members: members.data.length }; // Should delegate to subagent + } +}); +``` + +**Keep Subagents Independent:** +- Minimize dependencies on parent +- Use parent for validation, not core logic +- Each subagent should have clear responsibility + +**Limit Depth to One Level:** +```typescript +// Supported +team/members/agent.ts + +// Not supported +team/members/admins/agent.ts // Too deep + +// Alternative +team/members/agent.ts +team/memberAdmins/agent.ts // Separate subagent +``` + +### Performance + +**Cache parent context if calling multiple times:** +```typescript +const membersAgent = createAgent({ + handler: async (ctx, input) => { + const parentData = ctx.parent ? await ctx.parent.run({ teamId: input.teamId }) : null; + // Use parentData multiple times without re-calling + return { processed: true }; + } +}); +``` + +**Execute independent subagent calls in parallel:** +```typescript +// Good +const [members, tasks] = await Promise.all([ + ctx.agent.team.members.run({ action: 'count' }), + ctx.agent.team.tasks.run({ action: 'count' }) +]); + +// Bad - slower when independent +const members = await ctx.agent.team.members.run({ action: 'count' }); +const tasks = await ctx.agent.team.tasks.run({ action: 'count' }); +``` + +### Testing + +**Test parent and children independently:** + +```typescript +// Test parent with mocked subagents +describe('Team Agent', () => { + it('should coordinate subagents', async () => { + const mockCtx = { + agent: { + team: { + members: { run: jest.fn().mockResolvedValue({ count: 5 }) }, + tasks: { run: jest.fn().mockResolvedValue({ count: 10 }) } + } + } + }; + const result = await teamAgent.handler(mockCtx, { action: 'stats' }); + expect(result.memberCount).toBe(5); + }); +}); + +// Test subagent with mocked parent +describe('Members Subagent', () => { + it('should validate via parent', async () => { + const mockCtx = { + parent: { run: jest.fn().mockResolvedValue({ authorized: true }) }, + kv: { get: jest.fn(), set: jest.fn() } + }; + await membersAgent.handler(mockCtx, { action: 'add', name: 'Alice' }); + expect(mockCtx.parent.run).toHaveBeenCalled(); + }); +}); +``` + +### Error Handling + +**Let validation errors propagate:** +```typescript +const membersAgent = createAgent({ + handler: async (ctx, input) => { + if (ctx.parent) { + await ctx.parent.run({ userId: input.userId, teamId: input.teamId }); + // Parent throws on validation failure - error propagates to caller + } + return { members: [] }; + } +}); +``` + +**Graceful degradation for optional parent calls:** +```typescript +const membersAgent = createAgent({ + handler: async (ctx, input) => { + if (ctx.parent) { + try { + await ctx.parent.run({ userId: input.userId }); + } catch (error) { + ctx.logger.warn('Parent validation unavailable', { error }); + // Continue with degraded functionality + } + } + return { members: [] }; + } +}); +``` + +## React Integration + +The SDK generates type-safe React hooks for accessing subagents: + +```typescript +// Generated types +export type AgentClient = { + team: { + run: (input: TeamInput) => Promise; + members: { + run: (input: MembersInput) => Promise; + }; + }; +}; + +// Usage in React components +import { useAgent } from '@/lib/agentuity/client/react'; + +export function TeamDashboard() { + const loadData = async () => { + // Nested property access with full type safety + const teamInfo = await useAgent.team.run({ action: 'stats' }); + const members = await useAgent.team.members.run({ action: 'list' }); + + // TypeScript knows the exact types + console.log(members.members.length); + }; +} +``` + +**Key Points:** +- Nested property access: `useAgent.parent.child.run()` +- Full TypeScript inference +- Same patterns as backend usage + +## Next Steps + +**Explore Related Guides:** +- [Routing & Triggers](/Guides/routing-triggers) - HTTP methods, WebSocket, SSE, and specialized routes +- [Schema Validation](/Guides/schema-validation) - Type-safe schemas with Zod/Valibot/ArkType +- [Events](/Guides/events) - Monitor agent execution with lifecycle hooks +- [Sessions & Threads](/Guides/sessions-threads) - Stateful context management + +**API Reference:** +- [AgentContext](/api-reference#agentcontext) - Complete context API including `ctx.parent` +- [createAgent](/api-reference#createagent) - Agent creation and configuration +- [createRouter](/api-reference#createrouter) - Route definition and middleware + +**Examples:** +- [Examples](/Examples) - Additional patterns and complete working examples diff --git a/content/v1/Guides/vector-storage.mdx b/content/v1/Guides/vector-storage.mdx new file mode 100644 index 00000000..3f96aa80 --- /dev/null +++ b/content/v1/Guides/vector-storage.mdx @@ -0,0 +1,954 @@ +--- +title: Vector Storage +description: Semantic search and retrieval for knowledge bases, RAG systems, and agent memory +--- + +# Vector Storage + +Vector storage enables semantic search for your agents, allowing them to find information by meaning rather than keywords. Use it for knowledge bases, RAG systems, and persistent agent memory. + +## When to Use Vector Storage + +Vector storage enables semantic search for your agents, allowing them to find information by meaning rather than keywords. Ideal for knowledge bases, RAG systems, and persistent agent memory. + +Choose the right storage for your use case: + +- **Vector Storage**: Semantic search, embeddings, similarity matching +- **[Key-Value Storage](/Guides/key-value-storage)**: Fast lookups, simple data, temporary state +- **[Object Storage](/Guides/object-storage)**: Large files, media, backups + +## Understanding Vector Storage + +Vector storage works by converting text into high-dimensional numerical representations (embeddings) that capture semantic meaning. When you search, the system finds documents with similar meanings rather than just keyword matches. + +**Key use cases:** +- Knowledge bases and documentation search +- Long-term memory across agent sessions +- RAG systems combining retrieval with AI generation +- Semantic similarity search + +## Managing Vector Instances + +### Viewing Vector Storage in the Cloud Console + +Navigate to **Services > Vector** in the Agentuity Cloud Console to view all your vector storage instances. The interface shows: + +- **Database Name**: The identifier for your vector storage +- **Projects**: Which projects are using this storage +- **Agents**: Which agents have access +- **Size**: Storage utilization + +You can filter instances by name using the search box and create new vector storage instances with the **Create Storage** button. + +TODO: Add screenshot of Vector Storage Overview + +### Creating Vector Storage + +You can create vector storage either through the Cloud Console or programmatically in your agent code. + +#### Via Cloud Console + +Navigate to **Services > Vector** and click **Create Storage**. Choose a descriptive name that reflects the storage purpose (e.g., `knowledge-base`, `agent-memory`, `product-catalog`). + +#### Via SDK + +Vector storage is created automatically when your agent first calls `ctx.vector.upsert()` with an instance name: + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const agent = createAgent({ + handler: async (ctx, input) => { + // Storage 'knowledge-base' is auto-created if it doesn't exist + await ctx.vector.upsert('knowledge-base', { + key: 'doc-1', + document: 'Agentuity is an agent-native cloud platform', + metadata: { category: 'platform' } + }); + + return { success: true }; + } +}); +``` + +## Vector Storage API + +All vector operations are accessed through `ctx.vector` in your agent handler. The API provides type-safe methods for upserting, searching, retrieving, and deleting vectors. + +### Upserting Documents + +The `upsert` operation inserts new documents or updates existing ones. You can provide either text (which gets automatically converted to embeddings) or pre-computed embeddings. + +**Key Requirement:** +- All documents require a `key` field as a unique identifier + +**Idempotent Behavior:** +The upsert operation is idempotent - upserting with an existing key updates the existing vector rather than creating a duplicate. The same internal vector ID is reused, ensuring your vector storage remains clean and efficient. + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const agent = createAgent({ + handler: async (ctx, input) => { + // Upsert documents with text (automatic embedding) + const results = await ctx.vector.upsert( + 'knowledge-base', + { + key: 'doc-1', + document: 'Agentuity is an agent-native cloud platform', + metadata: { category: 'platform', source: 'docs' } + }, + { + key: 'doc-2', + document: 'Vector storage enables semantic search capabilities', + metadata: { category: 'features', source: 'docs' } + } + ); + + // Returns array of {key, id} mappings + // results = [ + // { key: 'doc-1', id: 'internal-id-123' }, + // { key: 'doc-2', id: 'internal-id-456' } + // ] + + return { insertedCount: results.length }; + } +}); +``` + +**Upsert with Pre-computed Embeddings:** + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + // Upsert with pre-computed embeddings + const embeddingResults = await ctx.vector.upsert( + 'custom-embeddings', + { + key: 'embedding-1', + embeddings: [0.1, 0.2, 0.3, 0.4], + metadata: { id: 'doc-1', type: 'custom' } + } + ); + + return { success: true }; + } +}); +``` + +**Return Value:** +- Returns `VectorUpsertResult[]` - array of `{ key: string, id: string }` objects +- The `key` matches your provided key +- The `id` is the internal vector ID assigned by the service + +### Searching Vector Storage + +Search operations find semantically similar documents based on a text query. You can control the number of results, similarity threshold, and filter by metadata. + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const agent = createAgent({ + handler: async (ctx, input) => { + // Basic semantic search + const results = await ctx.vector.search('knowledge-base', { + query: 'What is an agent platform?', + limit: 5, + similarity: 0.7, + metadata: { category: 'platform' } + }); + + // Process results + const formattedResults = results.map(result => ({ + content: result.metadata?.text, + source: result.metadata?.source, + similarity: result.similarity + })); + + return { results: formattedResults }; + } +}); +``` + +**Search Parameters:** +- `query` (required): Text query to search for +- `limit` (optional): Maximum number of results to return +- `similarity` (optional): Minimum similarity threshold (0.0-1.0) +- `metadata` (optional): Filter results by metadata key-value pairs + +**Search Results:** +Each result includes: +- `id`: Internal vector ID +- `key`: Your unique key +- `similarity`: Score from 0.0-1.0 (1.0 = perfect match) +- `metadata`: Your stored metadata object + +**Type Safety with Generics:** + +```typescript +interface DocumentMetadata { + title: string; + category: string; + source: string; + text: string; +} + +const agent = createAgent({ + handler: async (ctx, input) => { + // Specify metadata type for type-safe access + const results = await ctx.vector.search('knowledge-base', { + query: input.question, + limit: 3, + similarity: 0.7 + }); + + // TypeScript knows the shape of metadata + const titles = results.map(r => r.metadata?.title); + const categories = results.map(r => r.metadata?.category); + + return { titles, categories }; + } +}); +``` + +### Retrieving Vectors by Key + +The `get` method retrieves a specific vector directly using its key, without performing a similarity search. Returns a discriminated union for type-safe null checking. + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const agent = createAgent({ + handler: async (ctx, input) => { + // Direct lookup by key + const result = await ctx.vector.get('knowledge-base', 'doc-1'); + + if (result.exists) { + // TypeScript knows result.data exists here + console.log(`Found: ${result.data.id}`); + console.log(`Key: ${result.data.key}`); + console.log('Metadata:', result.data.metadata); + } else { + console.log('Vector not found'); + } + + return { found: result.exists }; + } +}); +``` + +**Type-Safe with Discriminated Union:** + +The `get` method returns a discriminated union that TypeScript can narrow based on the `exists` property: + +```typescript +interface UserPreferences { + theme: string; + language: string; +} + +const agent = createAgent({ + handler: async (ctx, input) => { + const result = await ctx.vector.get('user-prefs', input.userId); + + // Type-safe access + if (result.exists) { + // TypeScript knows result.data is available + const theme = result.data.metadata?.theme; + const language = result.data.metadata?.language; + return { theme, language }; + } + + // Handle not found + return { theme: 'default', language: 'en' }; + } +}); +``` + +**Common Use Cases:** + +```typescript +// 1. Check if a vector exists before updating +const existing = await ctx.vector.get('products', 'product-123'); +if (existing.exists) { + // Update with merged metadata + await ctx.vector.upsert('products', { + key: 'product-123', + document: 'Updated product description', + metadata: { + ...(existing.data.metadata ?? {}), + lastUpdated: Date.now() + } + }); +} + +// 2. Retrieve full metadata after search +const searchResults = await ctx.vector.search('products', { + query: 'office chair', + limit: 5 +}); + +// Get complete details for the top result +if (searchResults[0]) { + const fullDetails = await ctx.vector.get('products', searchResults[0].key); + if (fullDetails.exists) { + console.log('Full metadata:', fullDetails.data.metadata); + } +} +``` + +**When to use `get` vs `search`:** +- Use `get` when you know the exact key (like a database primary key lookup) +- Use `search` when finding vectors by semantic similarity +- `get` is faster for single lookups since it doesn't compute similarities +- `get` returns `{ exists: false }` if the key is not found + +### Batch Retrieval with getMany() + +The `getMany` method retrieves multiple vectors by their keys in a single operation. Returns a `Map` for efficient lookups. + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const agent = createAgent({ + handler: async (ctx, input) => { + // Retrieve multiple documents at once + const keys = ['doc-1', 'doc-2', 'doc-3']; + const resultMap = await ctx.vector.getMany('knowledge-base', ...keys); + + // resultMap is a Map> + const documents = []; + for (const [key, result] of resultMap) { + documents.push({ + key, + content: result.document, + metadata: result.metadata + }); + } + + return { + requestedCount: keys.length, + foundCount: resultMap.size, + documents + }; + } +}); +``` + +**Use Cases:** + +```typescript +// 1. Fetch user's saved documents +const agent = createAgent({ + handler: async (ctx, input) => { + const savedKeys = input.userSavedDocIds; // ['faq-1', 'guide-5', 'tutorial-3'] + const docs = await ctx.vector.getMany('docs', ...savedKeys); + + return { + savedDocs: Array.from(docs.values()).map(d => ({ + title: d.metadata?.title, + summary: d.metadata?.summary + })) + }; + } +}); + +// 2. Hydrate search results with full content +const agent = createAgent({ + handler: async (ctx, input) => { + // First, search for relevant documents + const searchResults = await ctx.vector.search('kb', { + query: input.question, + limit: 10 + }); + + // Then batch-fetch full details for top 3 + const topKeys = searchResults.slice(0, 3).map(r => r.key); + const fullDocs = await ctx.vector.getMany('kb', ...topKeys); + + return { + results: Array.from(fullDocs.values()).map(doc => ({ + content: doc.document, + metadata: doc.metadata, + similarity: searchResults.find(r => r.key === doc.key)?.similarity + })) + }; + } +}); +``` + +**Key Points:** +- Returns `Map>` for efficient key-based lookups +- Only includes vectors that were found (missing keys are absent from the Map) +- More efficient than calling `get()` multiple times +- Results include full document content and embeddings + +### Checking Storage Existence + +The `exists` method checks if a vector storage instance exists without retrieving any data. + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const agent = createAgent({ + handler: async (ctx, input) => { + // Check before expensive operations + const hasKnowledgeBase = await ctx.vector.exists('knowledge-base'); + + if (!hasKnowledgeBase) { + return { + error: 'Knowledge base not initialized', + suggestion: 'Please upload documents first' + }; + } + + // Proceed with search + const results = await ctx.vector.search('knowledge-base', { + query: input.question + }); + + return { results }; + } +}); +``` + +**Common Use Cases:** + +```typescript +// 1. Conditional initialization +const agent = createAgent({ + handler: async (ctx, input) => { + const storageExists = await ctx.vector.exists('user-memory'); + + if (!storageExists) { + // Initialize with default documents + await ctx.vector.upsert('user-memory', { + key: 'welcome', + document: 'Welcome to the system', + metadata: { type: 'system' } + }); + } + + return { initialized: true }; + } +}); + +// 2. Feature availability check +const agent = createAgent({ + handler: async (ctx, input) => { + const features = { + search: await ctx.vector.exists('search-index'), + recommendations: await ctx.vector.exists('recommendations'), + history: await ctx.vector.exists('user-history') + }; + + return { availableFeatures: features }; + } +}); +``` + +**Performance Note:** +- Lightweight operation for checking availability +- Useful before expensive search operations +- Helps provide better error messages to users + +### Deleting Vectors + +Remove specific vectors from storage using their keys. + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const agent = createAgent({ + handler: async (ctx, input) => { + // Delete single vector + const deletedCount = await ctx.vector.delete('knowledge-base', 'doc-1'); + + // Delete multiple vectors + const bulkDeleteCount = await ctx.vector.delete( + 'knowledge-base', + 'doc-1', 'doc-2', 'doc-3' + ); + + return { deletedCount: bulkDeleteCount }; + } +}); +``` + +**Return Value:** +- Returns the count of deleted vectors +- Returns `0` if no keys were provided or if keys don't exist + +**Use Cases:** + +```typescript +// 1. Remove outdated documents +const agent = createAgent({ + handler: async (ctx, input) => { + const outdatedKeys = ['old-doc-1', 'deprecated-faq']; + const count = await ctx.vector.delete('docs', ...outdatedKeys); + + ctx.logger.info(`Removed ${count} outdated documents`); + return { removed: count }; + } +}); + +// 2. User data deletion +const agent = createAgent({ + handler: async (ctx, input) => { + // Find all user's vectors + const userVectors = await ctx.vector.search('user-data', { + metadata: { userId: input.userId }, + limit: 1000 + }); + + // Delete all user's data + const keys = userVectors.map(v => v.key); + const deletedCount = await ctx.vector.delete('user-data', ...keys); + + return { + message: `Deleted ${deletedCount} user records`, + userId: input.userId + }; + } +}); +``` + +## Practical Examples + +### Building a Simple RAG System + +This example demonstrates a complete Retrieval-Augmented Generation (RAG) pattern - searching for relevant context and using it to generate informed responses. + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; +import { generateText } from 'ai'; +import { openai } from '@ai-sdk/openai'; + +const ragAgent = createAgent({ + schema: { + input: z.object({ question: z.string() }), + output: z.object({ + answer: z.string(), + sources: z.array(z.object({ + id: z.string(), + key: z.string(), + title: z.string().optional(), + similarity: z.number() + })) + }) + }, + handler: async (ctx, input) => { + try { + // 1. Search for relevant context (top 5 results) + const searchResults = await ctx.vector.search('knowledge-base', { + query: input.question, + limit: 5, + similarity: 0.7 + }); + + // 2. Handle no results gracefully + if (searchResults.length === 0) { + return { + answer: "I couldn't find relevant information to answer your question.", + sources: [] + }; + } + + // 3. Assemble context from search results + const contextTexts = searchResults.map(result => + result.metadata?.content ?? result.metadata?.text ?? '' + ); + const assembledContext = contextTexts.join('\n\n'); + + // 4. Generate response using context + const { text } = await generateText({ + model: openai('gpt-4o-mini'), + prompt: `Answer the question based on the following context: + +Context: ${assembledContext} + +Question: ${input.question} + +Answer:` + }); + + // 5. Return answer with sources + return { + answer: text, + sources: searchResults.map(r => ({ + id: r.id, + key: r.key, + title: r.metadata?.title, + similarity: r.similarity + })) + }; + + } catch (error) { + ctx.logger.error('RAG query failed:', error); + throw error; + } + } +}); +``` + +**Key Points:** +- **Semantic search** finds relevant documents based on meaning, not keywords +- **Similarity threshold** of 0.7 balances relevance with recall +- **Context assembly** combines multiple sources for comprehensive answers +- **Error handling** ensures graceful failures with helpful messages +- **Source attribution** provides transparency about where information came from + + +For production RAG systems, add evaluations to check answer quality, hallucination detection, and faithfulness to sources. See the [Evaluations guide](/Guides/evaluations) for comprehensive RAG quality patterns including: +- **Hallucination Detection** - Verify answers are grounded in retrieved documents +- **RAG Quality Metrics** - Measure contextual relevancy, answer relevancy, and faithfulness +- **Using ctx.state** - Store retrieved documents for eval access + +These evaluation patterns help ensure your RAG system produces high-quality, trustworthy responses. + + +### Semantic Search with Metadata Filtering + +This example shows how to combine semantic similarity with metadata filters for precise results - like finding products that match both meaning and business criteria. + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const productSearchAgent = createAgent({ + schema: { + input: z.object({ + query: z.string(), + maxPrice: z.number().optional(), + category: z.string().optional(), + inStock: z.boolean().optional() + }), + output: z.object({ + query: z.string(), + filters: z.record(z.unknown()), + resultCount: z.number(), + products: z.array(z.record(z.unknown())) + }) + }, + handler: async (ctx, input) => { + try { + // Build metadata filters based on criteria + const metadataFilters: Record = {}; + if (input.category) metadataFilters.category = input.category; + if (input.inStock !== undefined) metadataFilters.inStock = input.inStock; + + // Search with semantic similarity + metadata filters + const searchResults = await ctx.vector.search('products', { + query: input.query, + limit: 10, + similarity: 0.65, // Lower threshold for broader results + metadata: metadataFilters + }); + + // Post-process: Apply price filter and sort by relevance + const filteredResults = searchResults + .filter(result => !input.maxPrice || (result.metadata?.price as number) <= input.maxPrice) + .map(result => ({ + ...result.metadata, + similarity: result.similarity + })) + .sort((a, b) => (b.similarity as number) - (a.similarity as number)); + + return { + query: input.query, + filters: { + maxPrice: input.maxPrice, + category: input.category, + inStock: input.inStock + }, + resultCount: filteredResults.length, + products: filteredResults.slice(0, 5) // Top 5 results + }; + + } catch (error) { + ctx.logger.error('Product search failed:', error); + return { + query: input.query, + filters: {}, + resultCount: 0, + products: [] + }; + } + } +}); +``` + +**Key Techniques:** +- **Metadata filters** are applied at the vector search level for efficiency +- **Post-processing** handles filters that can't be done at search time (like price ranges) +- **Lower similarity threshold** (0.65) catches more potential matches when using strict filters +- **Type-safe schemas** ensure input validation and output consistency + +## Common Pitfalls & Solutions + +### Empty Search Results + +**Problem**: Your search returns empty results even though relevant data exists. + +**Solutions:** +- **Lower the similarity threshold**: Start at 0.5 and increase gradually +- **Check your metadata filters**: They use exact matching, not fuzzy matching +- **Verify document format**: Ensure documents were upserted with text content + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + // Adaptive threshold example + let results = await ctx.vector.search('kb', { + query: input.question, + similarity: 0.8 + }); + + if (results.length === 0) { + // Try again with lower threshold + results = await ctx.vector.search('kb', { + query: input.question, + similarity: 0.5 + }); + } + + return { results }; + } +}); +``` + +### Duplicate Documents + +**Problem**: Same content appears multiple times in search results. + +**Solution**: Vector upsert is idempotent when using the same key: +- Always use consistent `key` values for your documents +- Upserting with an existing key updates the vector rather than creating a duplicate +- The same internal vector ID is reused, keeping your storage clean + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + // This will update existing doc-1, not create a duplicate + await ctx.vector.upsert('docs', { + key: 'doc-1', + document: 'Updated content', + metadata: { version: 2 } + }); + + return { success: true }; + } +}); +``` + +### Performance Issues + +**Problem**: Vector operations take too long. + +**Solutions:** +- **Batch operations**: Upsert 100-500 documents at once, not one by one +- **Limit search results**: Use `limit: 10` instead of retrieving all matches +- **Optimize metadata**: Keep metadata objects small and focused +- **Use getMany()**: Batch retrieval instead of multiple `get()` calls + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + // Good: Batch upsert + const documents = input.docs.map(doc => ({ + key: doc.id, + document: doc.content, + metadata: { title: doc.title } + })); + await ctx.vector.upsert('kb', ...documents); + + // Good: Limited search + const results = await ctx.vector.search('kb', { + query: input.query, + limit: 10 + }); + + return { count: documents.length, results }; + } +}); +``` + +### Irrelevant Search Results + +**Problem**: Search returns irrelevant or unexpected documents. + +**Solutions:** +- **Check similarity scores**: Results with similarity < 0.7 may be poor matches +- **Review metadata filters**: Remember they're AND conditions, not OR +- **Verify embeddings**: Ensure consistent text preprocessing before upserting + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + const results = await ctx.vector.search('kb', { + query: input.question, + limit: 10, + similarity: 0.7 // Minimum quality threshold + }); + + // Filter by additional quality checks + const qualityResults = results.filter(r => { + // Only include high-quality matches + return r.similarity >= 0.75 && r.metadata?.verified === true; + }); + + return { results: qualityResults }; + } +}); +``` + +## Best Practices + +### Document Structure + +- **Include context in documents**: Store enough context so documents are meaningful when retrieved +- **Use descriptive metadata**: Include relevant metadata for filtering and identification +- **Consistent formatting**: Use consistent document formatting for better embeddings + +```typescript +// Good: Rich, contextual documents +await ctx.vector.upsert('docs', { + key: 'getting-started', + document: 'Getting Started with Agentuity: This guide walks you through creating your first agent, including setup, configuration, and deployment.', + metadata: { + title: 'Getting Started Guide', + category: 'documentation', + tags: ['beginner', 'setup', 'tutorial'], + lastUpdated: Date.now() + } +}); + +// Avoid: Minimal context +await ctx.vector.upsert('docs', { + key: 'doc1', + document: 'Setup guide', + metadata: { type: 'doc' } +}); +``` + +### Search Optimization + +- **Adjust similarity thresholds**: Start with 0.7 and adjust based on result quality +- **Use metadata filtering**: Combine semantic search with metadata filters for precise results +- **Limit result sets**: Use appropriate limits to balance performance and relevance + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + // Optimized search with filters + const results = await ctx.vector.search('products', { + query: input.searchTerm, + limit: 20, // Reasonable limit + similarity: 0.7, // Quality threshold + metadata: { + active: true, // Only active products + category: input.category + } + }); + + return { results }; + } +}); +``` + +### Performance Considerations + +- **Batch upsert operations**: Use bulk upsert instead of individual calls +- **Monitor storage usage**: Track vector storage size in the Cloud Console +- **Consider document chunking**: Break large documents into smaller, focused chunks +- **Use type parameters**: Leverage TypeScript generics for type-safe metadata access + +```typescript +// Good: Batch operations +const agent = createAgent({ + handler: async (ctx, input) => { + // Process documents in batches + const batchSize = 100; + const documents = input.documents; + + for (let i = 0; i < documents.length; i += batchSize) { + const batch = documents.slice(i, i + batchSize); + const vectors = batch.map(doc => ({ + key: doc.id, + document: doc.content, + metadata: { title: doc.title, category: doc.category } + })); + + await ctx.vector.upsert('kb', ...vectors); + } + + return { processed: documents.length }; + } +}); +``` + +### Type Safety + +Use TypeScript interfaces for consistent, type-safe metadata: + +```typescript +interface DocumentMetadata { + title: string; + category: 'guide' | 'api' | 'tutorial'; + tags: string[]; + author: string; + lastUpdated: number; +} + +const agent = createAgent({ + handler: async (ctx, input) => { + // Type-safe upsert + await ctx.vector.upsert('docs', { + key: 'example', + document: 'Content here', + metadata: { + title: 'Example', + category: 'guide', + tags: ['intro'], + author: 'system', + lastUpdated: Date.now() + } + }); + + // Type-safe search + const results = await ctx.vector.search('docs', { + query: input.question, + metadata: { category: 'guide' } + }); + + // TypeScript knows the metadata structure + const titles = results.map(r => r.metadata?.title); + + return { titles }; + } +}); +``` + +## Integration with Agent Memory + +Vector storage serves as long-term memory for agents, enabling them to: + +- Remember past conversations and context across sessions +- Access organizational knowledge bases +- Retrieve relevant examples for few-shot learning +- Build and maintain agent-specific knowledge repositories + +For more information on memory patterns, see the [Key-Value Storage guide](/Guides/key-value-storage) for short-term memory or explore [Agent Communication](/Guides/agent-communication) for sharing knowledge between agents. + +## Storage Types Overview + +TODO: Add video for v1 storage overview + +## Next Steps + +- [Evaluations](/Guides/evaluations) - Add quality checks for RAG systems (hallucination detection, faithfulness, relevancy) +- [Key-Value Storage](/Guides/key-value-storage) - Fast lookups for simple data +- [Sessions and Threads](/Guides/sessions-threads) - Manage conversation state +- [API Reference](/SDK/api-reference) - Complete vector storage API documentation diff --git a/content/v1/Introduction/architecture.mdx b/content/v1/Introduction/architecture.mdx new file mode 100644 index 00000000..a5ac2d82 --- /dev/null +++ b/content/v1/Introduction/architecture.mdx @@ -0,0 +1,426 @@ +--- +title: Overview +description: Understanding Agentuity +--- + +## Agentuity Overview + +Agentuity is a cloud platform that lets you run and scale AI agents with enterprise-grade reliability. +Each agent operates in its own isolated, containerized environment, giving you the security and +isolation you need for production workloads. + +Unlike traditional serverless platforms, your agents run for as long as needed, maintaining state +and context throughout their lifecycles. This long-running approach means you can build complex +workflows without worrying about time limits. It's perfect for agents that need extended processing +time, persistent storage, or ongoing access to resources. + +The platform is fundamentally cross-platform, so you can run different agent frameworks (CrewAI, +Langchain, custom agents) side by side in the same ecosystem. Built-in communication channels let +your agents work together seamlessly, regardless of which framework they use. + +## Core Components + +Agentuity consists of five primary components: + +1. **Agent Platform** - The cloud platform for providing agent services, providing: + - Agent communication and routing + - Agent monitoring, logging, telemetry and troubleshooting + - Agent usage analytics and performance insights + - Automatic scaling on-demand based on workload + - Agent services such as KeyValue, Vector storage, AI Gateway and more + +2. **Agent Runtime** - The execution environment where your agents run, providing: + - Isolated, secure virtualized environment for each agent project + - Resource management and optimization + - Long-running support for persistent agents + - Dynamic Storage, Compute and Networking resources + +3. **Command Line Interface (CLI)** - A developer tool that enables: + - Quick agent creation and initialization + - Local development and testing + - Deployment management to the Agentuity cloud + - Integration with external Agentic code tools via MCP + +4. **Software Development Kits (SDKs)** - Libraries that provide: + - Agent-native tools and services integration with the Agent Platform + - Runtime-specific optimizations for JavaScript/TypeScript (Node.js and Bun) + - Integration capabilities with external systems + - Enhanced agent capabilities and extensions which work cross-framework and cross-runtime + +5. **Web Console** - A management interface offering: + - Real-time agent monitoring and metrics + - Deployment and configuration management + - Usage analytics, logging, monitoring and performance insights + - Team collaboration features + +## SDK Architecture + +Agentuity v1 uses a monorepo structure with specialized packages for different concerns: + +### Package Overview + +- **`@agentuity/runtime`** - The primary SDK for building agents + - Agent creation and lifecycle management + - Built on Hono framework for routing + - Event system for monitoring + - Evaluation framework for testing + - Session and thread management + +- **`@agentuity/core`** - Shared utilities and types + - Storage abstractions (KV, Vector, Object, Stream) + - Common interfaces and types + - Schema validation support (StandardSchema) + +- **`@agentuity/server`** - Runtime-agnostic server utilities + - Server creation and configuration + - Request/response handling + - Middleware support + +- **`@agentuity/react`** - React components and hooks + - `AgentuityProvider` for app-wide configuration + - `useAgent` hook for calling agents + - `useAgentWebsocket` for WebSocket connections + - `useAgentEventStream` for Server-Sent Events + +- **`@agentuity/cli`** - Command-line tools + - Project scaffolding and management + - Local development server + - Deployment orchestration + +### App & Router Architecture + +Agentuity v1 is built on [Hono](https://hono.dev/), a fast, lightweight web framework. Your agents are organized into an **App** that manages routing, configuration, and lifecycle: + +```typescript +import { createApp } from '@agentuity/runtime'; + +// Create the app +const app = createApp(); + +// The router is available at app.router +app.router.post('/my-agent', async (c) => { + return c.json({ message: 'Hello!' }); +}); + +// Export the server +export default app.server; +``` + +This architecture provides: +- **Type-safe routing** with full TypeScript support +- **Middleware support** for authentication, logging, etc. +- **Automatic service injection** (kv, logger, tracer, etc.) +- **Event system** for monitoring agent executions +- **Code-based trigger configuration** - Cron schedules, email addresses, and SMS numbers are defined in your routes, not in the UI + +## Data Flow + +Agent communication and data flow in Agentuity follow secure, encrypted channels: + +1. **Agent-to-Agent Communication** - Agents can communicate with each other through authenticated, encrypted routing, +regardless of the underlying frameworks or runtimes used. + +2. **External Integrations** - Agents can connect to external systems and data sources through managed +integration points. + +3. **Deployment Pipeline** - Your project code is packaged, containerized, and deployed to the Agentuity +cloud infrastructure with appropriate networking and routing configured automatically. Built-in support for GitHub Actions. + +## Scalability + +Agentuity is designed for enterprise-scale agent deployments: + +- **Horizontal Scaling** - Automatically provision additional resources as demand increases +- **Framework Agnostic** - Scale any type of agent regardless of the underlying framework +- **Load Balancing** - Distribute agent workloads efficiently across available resources +- **Resource Optimization** - Intelligently allocate compute resources based on agent requirements + +## Security Architecture + +Security is foundational to Agentuity's design: + +- **Agent Isolation** - Each agent project operates in its own isolated environment +- **Encrypted Communications** - All agent-to-agent communication is encrypted +- **Secure Deployment** - Protected deployment pipeline from development to production + +## Project Conventions + +Agentuity projects follow specific conventions to take advantage of the deployment and cloud platform Agentuity offers. Understanding these conventions is important for effective agent development. + +### Project Structure + +Every Agentuity project requires the following core components: + +1. **agentuity.yaml** - The central configuration file that defines: + - Project metadata (name, ID, description) + - Development settings (port, watch patterns) + - Deployment configuration (resources, scaling) + - Bundler settings (language, runtime) + - Agent definitions and routing + +2. **Environment Variables** - Stored in a `.env` file: + - `AGENTUITY_SDK_KEY` - Identifies the SDK level API Key (only used in development to access the Agentuity Cloud) + - `AGENTUITY_PROJECT_KEY` - Identifies the project level API Key + - Additional provider-specific keys (OpenAI, Anthropic, etc.) + +3. **Agent Directory** - Specified in `bundler.agents.dir`: + - Each agent has its own subdirectory + - TypeScript/JavaScript entry points: `agent.ts` and `route.ts` + - Agent-specific configuration and dependencies + +### JavaScript/TypeScript Project Structure + +``` +my-project/ +├── agentuity.yaml # Project configuration +├── .env # Environment variables +├── package.json # Dependencies and scripts +├── app.ts # App entry point +└── src/ + ├── agents/ # Agent directory + │ └── my-agent/ # Individual agent + │ ├── agent.ts # Agent logic (createAgent) + │ └── route.ts # Route definitions (createRouter) + └── apis/ # Optional: Custom API routes + └── status/ + └── route.ts +``` + +### Agent Structure + +Each agent in Agentuity consists of two files that work together: + +#### Understanding the Two-File Pattern + +Every agent directory contains both `agent.ts` and `route.ts`. This separation keeps concerns clear: + +- **`agent.ts`** uses `createAgent()` to define the agent's logic, schemas, and metadata. This is where your business logic lives. +- **`route.ts`** uses `createRouter()` to define HTTP endpoints that invoke the agent. Routes handle HTTP-specific concerns like request parsing and response formatting. + +The route file imports the agent and calls it using `ctx.agent.agentName.run(input)`. The Agentuity bundler automatically discovers both files and registers them together based on the directory structure. + +Routes can exist without agents (such as health check endpoints in `/apis/` directories), but every agent should have a corresponding route file to enable HTTP access. + +#### `agent.ts` - Agent Logic + +Defines the agent's behavior, schema, and metadata: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +export default createAgent({ + schema: { + input: z.object({ + message: z.string() + }), + output: z.object({ + response: z.string() + }) + }, + metadata: { + name: 'My Agent', + description: 'A simple agent' + }, + handler: async (ctx, input) => { + ctx.logger.info('Processing request', { input }); + + return { + response: `Received: ${input.message}` + }; + } +}); +``` + +#### `route.ts` - Route Definitions + +Defines how the agent is exposed via HTTP or other protocols: + +```typescript +import { createRouter } from '@agentuity/runtime'; +import agent from './agent'; + +const router = createRouter(); + +// HTTP routes +router.post('/', async (c) => { + const input = await c.req.json(); + const result = await c.agent.myAgent.run(input); + return c.json(result); +}); + +// Advanced: WebSocket route +router.websocket('/ws', (c) => (ws) => { + ws.onMessage(async (event) => { + const result = await c.agent.myAgent.run(event.data); + ws.send(JSON.stringify(result)); + }); +}); + +export default router; +``` + +#### How Agent Files Work Together + +When an HTTP request arrives, the flow works as follows: + +1. The HTTP request is routed to the appropriate route handler in `route.ts` +2. The route handler optionally validates the input using middleware (such as `zValidator` with the agent's schema) +3. The route calls the agent using `c.agent.myAgent.run(input)` +4. The agent's handler function executes with the validated input +5. The agent returns its output, which flows back through the route to the client + +This architecture provides clear separation: routes handle HTTP concerns (request parsing, validation, response formatting) while agents focus purely on business logic. The agent can be called from multiple routes or by other agents without duplicating logic. + +### App Entry Point + +The `app.ts` file creates and configures your application: + +```typescript +import { createApp } from '@agentuity/runtime'; + +const app = createApp(); + +// Optional: Add app-level event listeners +app.addEventListener('agent.started', (event, agent, ctx) => { + app.logger.info('Agent started', { + agent: agent.metadata.name, + session: ctx.sessionId + }); +}); + +// The router automatically discovers and registers all agents +// based on the directory structure + +export default app.server; +``` + +### Configuration File (agentuity.yaml) + +The `agentuity.yaml` file is the heart of your project, defining how it behaves in development and production: + +```yaml +version: ">=0.0.0" # Minimum CLI version required +project_id: "proj_..." # Unique project identifier +name: "My Project" # Human-readable project name +description: "..." # Optional project description + +# Development configuration +development: + port: 3000 # Local development server port + watch: + enabled: true # Auto-reload on file changes + files: ["src/**/*.ts"] # Files to watch + +# Deployment configuration +deployment: + resources: + memory: "1Gi" # Memory allocation + cpu: "1000m" # CPU allocation + +# Bundler configuration +bundler: + language: "javascript" # Programming language (javascript only in v1) + runtime: "nodejs" # Runtime environment (nodejs or bunjs) + agents: + dir: "src/agents" # Directory where agents are located + +# Agents configuration +agents: + - id: "agent_..." # Unique agent identifier + name: "My Agent" # Human-readable agent name + description: "..." # Optional agent description +``` + +### Key Concepts in v1 + +#### Sessions and Threads + +- **Session** - Represents a single execution context with a unique `sessionId` +- **Thread** - Represents a conversation thread for multi-turn interactions +- Access via `ctx.session` and `ctx.thread` in agent handlers + +#### Event System + +Monitor agent lifecycle with event listeners: + +```typescript +// Agent-level events +agent.addEventListener('started', (event, agent, ctx) => { + // Called when agent starts +}); + +agent.addEventListener('completed', (event, agent, ctx) => { + // Called when agent completes successfully +}); + +agent.addEventListener('errored', (event, agent, ctx, error) => { + // Called when agent encounters an error +}); + +// App-level events +app.addEventListener('agent.started', (event, agent, ctx) => { + // Called for any agent start +}); +``` + +#### Evaluation Framework + +Test and validate agent outputs automatically: + +```typescript +agent.createEval({ + metadata: { + name: 'quality-check', + description: 'Validates output quality' + }, + handler: async (ctx, input, output) => { + // Evaluate the output + const score = calculateQuality(output); + + return { + success: true, + score: score, + metadata: { /* additional info */ } + }; + } +}); +``` + +#### Schema Validation + +Define and validate input/output schemas using any StandardSchema-compatible library (Zod, Valibot, ArkType, etc.): + +```typescript +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ + name: z.string(), + age: z.number().optional() + }), + output: z.object({ + greeting: z.string() + }) + }, + handler: async (ctx, input) => { + // input is fully typed and validated + return { + greeting: `Hello ${input.name}!` + }; + } +}); +``` + +### Why These Conventions Matter + +These conventions enable several key capabilities: + +1. **Consistent Development Experience** - Standardized structure makes it easier to work across projects +2. **Automated Deployment** - The CLI can package and deploy your project without additional configuration +3. **Framework Flexibility** - Use any agent framework while maintaining compatibility with the platform +4. **Type Safety** - Full TypeScript support throughout the stack +5. **Scalability** - Clear separation of concerns makes it easy to organize complex agent systems + +TODO: Add examples showing integration with frontend frameworks (Next.js, Svelte, vanilla JS, etc.) for building UIs that call agents diff --git a/content/v1/Introduction/core-concepts.mdx b/content/v1/Introduction/core-concepts.mdx new file mode 100644 index 00000000..b08989fc --- /dev/null +++ b/content/v1/Introduction/core-concepts.mdx @@ -0,0 +1,308 @@ +--- +title: Core Concepts +description: Learn about the fundamental concepts of the Agentuity v1 SDK +--- + +The Agentuity v1 SDK is built on modern web standards and designed for type safety. Understanding these core concepts will help you build robust agents quickly. + +## Creating Agents + +Each agent in v1 is created with the `createAgent()` function. Simply define your metadata and handler function to get started. + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const agent = createAgent({ + metadata: { + name: 'My Agent', + description: 'A simple agent that greets users' + }, + handler: async (ctx) => { + ctx.logger.info('Agent invoked'); + return { message: 'Hello!' }; + } +}); + +export default agent; +``` + +Each agent lives in its own directory with two files: +- **`agent.ts`** - Defines the agent logic using `createAgent()`, including schemas and the handler function +- **`route.ts`** - Defines HTTP routes using `createRouter()` that call the agent + +The route file imports the agent and invokes it via `ctx.agent.agentName.run(input)`. This separation keeps HTTP routing concerns separate from agent logic. + + +**Project Structure:** See the [Architecture Guide](/Introduction/architecture) for complete details on file structure, how routes and agents work together, and how the bundler discovers them. + + +## Schema Validation + +For type safety, it's recommended to add a schema. This enables IDE autocomplete and automatic input validation. + +v1 supports any [StandardSchema](https://github.com/standard-schema/standard-schema)-compatible library, including Zod, Valibot, and ArkType. Here's an example with Zod: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ + name: z.string(), + age: z.number().optional() + }), + output: z.object({ + greeting: z.string() + }) + }, + handler: async (ctx, input) => { + // input is fully typed and validated! + return { + greeting: `Hello ${input.name}!` + }; + } +}); +``` + +**Benefits:** +- Full TypeScript type inference +- Automatic runtime validation +- Clear API contracts +- Better error messages + +## App and Router + +v1 uses Hono for routing, a fast and lightweight web framework similar to Express. + +Your project has an `app.ts` file that creates the application and exports the server: + +```typescript +import { createApp } from '@agentuity/runtime'; + +const app = createApp(); + +// The router is available at app.router +// Routes are automatically discovered from agent directories + +export default app.server; +``` + +Agents define their routes in `route.ts` files using `createRouter()`: + +```typescript +import { createRouter } from '@agentuity/runtime'; +import agent from './agent'; + +const router = createRouter(); + +router.post('/', async (c) => { + const input = await c.req.json(); + const result = await c.agent.myAgent.run(input); + return c.json(result); +}); + +export default router; +``` + + +**Advanced Routing:** v1 supports WebSocket, Server-Sent Events, email, cron, and more. See the [Routing & Triggers Guide](/Guides/routing-triggers) for details. + + +## Agent Context + +The handler receives a context object (`ctx`) with everything your agent needs: + +**Execution Info:** +- `ctx.sessionId` - Unique identifier for this execution +- `ctx.session` - Session object for conversation state +- `ctx.thread` - Thread object for multi-turn interactions + +**Services:** +- `ctx.kv` - Key-value storage +- `ctx.vector` - Vector storage +- `ctx.objectstore` - Object storage +- `ctx.stream` - Stream storage + +**Agent Access:** +- `ctx.agent` - Call other agents in your project +- `ctx.current` - Reference to the current agent +- `ctx.parent` - Reference to parent agent (for subagents) + +**Observability:** +- `ctx.logger` - Structured logging +- `ctx.tracer` - OpenTelemetry tracing + +**Utilities:** +- `ctx.state` - Temporary state storage (Map) +- `ctx.waitUntil()` - Background task execution + +The context object provides access to all agent capabilities, from storage to logging. + +## Request Handling + +When using schemas, v1 automatically handles parsing and validation. Without schemas, use Hono's request methods for manual control. + +**Pattern 1: With Schema (Recommended)** + +When you define an input schema, it's passed directly to your handler: + +```typescript +const agent = createAgent({ + schema: { + input: z.object({ message: z.string() }) + }, + handler: async (ctx, input) => { + // input is already parsed and validated + return { echo: input.message }; + } +}); +``` + +**Pattern 2: Route-Based (Manual Parsing)** + +For more control, parse requests manually in your routes: + +```typescript +router.post('/', async (c) => { + const data = await c.req.json(); + const headers = c.req.header('authorization'); + const query = c.req.query('limit'); + + return c.json({ processed: data }); +}); +``` + +## Response Handling + +By default, returning an object will automatically serialize it as JSON. For more control over responses, use Hono's response methods. + +**Simple Returns:** + +```typescript +handler: async (ctx, input) => { + return { message: 'Done!' }; // Automatically becomes JSON +} +``` + +**Advanced Responses:** + +Use Hono methods in routes for custom status codes, headers, or content types: + +```typescript +router.get('/status', (c) => { + return c.json({ status: 'ok' }, 200); +}); + +router.get('/html', (c) => { + return c.html('

Hello

'); +}); + +router.get('/custom', (c) => { + return c.text('Custom response', 201, { + 'X-Custom-Header': 'value' + }); +}); +``` + +## Agent-to-Agent Communication + +To call another agent, use `ctx.agent.otherAgent.run()`. This provides direct access without requiring agent IDs or lookups. + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + // Call another agent + const result = await ctx.agent.helperAgent.run({ + data: input.data + }); + + // Use the result + return { + original: input.data, + processed: result + }; + } +}); +``` + +When both agents use schemas, the communication is fully type-safe. Your IDE will autocomplete the input structure and validate the output. + +## Sessions and Threads + +Sessions track individual executions, while threads track conversation histories. These enable stateful, conversational agents. + +**Sessions** represent a single execution context with a unique `sessionId`. Each agent invocation gets its own session. + +**Threads** represent conversation histories for multi-turn interactions. Use threads to maintain context across multiple requests. + +```typescript +handler: async (ctx, input) => { + // Log session info + ctx.logger.info('Session:', { + id: ctx.sessionId, + session: ctx.session + }); + + // Track conversation turns + const turnCount = (ctx.state.get('turnCount') as number || 0) + 1; + ctx.state.set('turnCount', turnCount); + + return { + turn: turnCount, + message: `This is turn ${turnCount} of our conversation` + }; +} +``` + + +**State Management:** Use `ctx.state` for temporary data within a session. For persistent storage, use `ctx.kv` or other storage services. + + +## Events and Evaluations + +v1 provides built-in capabilities for monitoring and testing agents through events and evaluations. + +**Events** let you listen to agent lifecycle events (started, completed, errored) for monitoring and analytics: + +```typescript +agent.addEventListener('started', (eventName, agent, ctx) => { + ctx.logger.info('Agent started', { name: agent.metadata.name }); +}); + +agent.addEventListener('completed', (eventName, agent, ctx) => { + ctx.logger.info('Agent completed successfully'); +}); +``` + +See the [Events Guide](/SDKs/javascript/events) for complete documentation. + +**Evaluations** automatically test agent outputs for quality, accuracy, or compliance: + +```typescript +agent.createEval({ + metadata: { + name: 'quality-check', + description: 'Validates output quality' + }, + handler: async (ctx, input, output) => { + const score = calculateQuality(output); + return { + success: true, + score: score + }; + } +}); +``` + +See the [Evaluations Guide](/SDKs/javascript/evaluations) for complete documentation. + +## Next Steps + +Now that you understand the core concepts, you can: + +- Explore the [Routing & Triggers Guide](/Guides/routing-triggers) for WebSocket, SSE, and specialized routes +- Learn about [Schema Validation](/SDKs/javascript/schema-validation) in depth +- Set up [Evaluations](/SDKs/javascript/evaluations) to test your agents +- Use [Events](/SDKs/javascript/events) for monitoring and analytics +- Check the [API Reference](/SDKs/javascript/api-reference) for complete documentation diff --git a/content/v1/Introduction/introduction.mdx b/content/v1/Introduction/introduction.mdx new file mode 100644 index 00000000..190f0cb3 --- /dev/null +++ b/content/v1/Introduction/introduction.mdx @@ -0,0 +1,38 @@ +--- +title: What is Agentuity? +description: Agentuity is rebuilding the cloud for AI Agents +--- + +
+ Build agents, not infrastructure +
+ +[Agentuity](https://agentuity.com) is a cloud platform designed specifically to make it easy to build, deploy, and operate AI Agents at scale. + +Our mission is to provide a fully agentic infrastructure and tools necessary to build Agents that are fully operated by AI. + + +If you're ready to dive in, skip to [Getting Started](/Introduction/getting-started) to get your first Agent up and running in minutes. + + + +With Agentuity, you or your agents can: + +- Deploy agents with a single command to a fully agentic infrastructure +- Monitor real-time performance, analytics, metrics and logs +- Auto scale agents effortlessly and on-demand +- Connect agents to various input and output channels (HTTP, WebSocket, email, SMS, cron, and more) +- Build type-safe agents with schema validation and automated testing +- Securely communicate between agents and build complex agentic workflows +- Create interactive UIs for your agents with React components +- Use any AI agent framework with Node.js or Bun + +

+We see a near future where Agents are the primary way to build and operate software and where all the infrastructure is built uniquely for them. +

+ +## Agentuity Platform Overview + +TODO: Add platform overview video + +If you're ready to have your own Agent, [Get Started](/Introduction/getting-started) in just a few minutes. \ No newline at end of file diff --git a/content/v1/SDK/api-reference.mdx b/content/v1/SDK/api-reference.mdx new file mode 100644 index 00000000..34723796 --- /dev/null +++ b/content/v1/SDK/api-reference.mdx @@ -0,0 +1,2693 @@ +--- +title: API Reference +description: Comprehensive reference for the Agentuity JavaScript SDK API +--- + +This section provides detailed documentation for the Agentuity JavaScript SDK API, including method signatures, parameters, return values, and example usage. + +## Table of Contents + +- [Agent Creation](#agent-creation) +- [Schema Validation](#schema-validation) +- [Agent Handler](#agent-handler) +- [Context API](#context-api) +- [Storage APIs](#storage-apis) +- [Logging](#logging) +- [Telemetry](#telemetry) +- [Agent Communication](#agent-communication) +- [Router & Routes](#router--routes) +- [Session & Thread Management](#session--thread-management) +- [Evaluations](#evaluations) +- [Event System](#event-system) + +--- + +## Agent Creation + +Agents are created using the `createAgent()` function, which provides type-safe agent definitions with built-in schema validation. + +### createAgent + +`createAgent(config: AgentConfig): Agent` + +Creates a new agent with schema validation and type inference. + +**Parameters** + +- `config`: Configuration object with the following properties: + - `schema` (optional): Object containing input and output schemas + - `input`: Schema for validating incoming data (Zod, Valibot, ArkType, or any StandardSchemaV1) + - `output`: Schema for validating outgoing data + - `stream`: Enable streaming responses (boolean, defaults to false) + - `handler`: The agent function that processes inputs and returns outputs + - `metadata` (optional): Agent metadata + - `name`: Display name for the agent + - `description`: Description of what the agent does + - `filename`: File path (auto-populated by bundler) + +**Return Value** + +Returns an `Agent` object with the following properties: +- `metadata`: Agent metadata (id, identifier, filename, version, name, description) +- `handler`: The internal handler function +- `createEval(config: EvalConfig): Eval`: Create quality evaluations for this agent +- `addEventListener(eventName, callback)`: Attach lifecycle event listeners +- `removeEventListener(eventName, callback)`: Remove event listeners +- `inputSchema` (conditional): Present if input schema is defined +- `outputSchema` (conditional): Present if output schema is defined +- `stream` (conditional): Present if streaming is enabled + +**Note**: To call agents from other agents, use the agent registry: `ctx.agent.agentName.run(input)` (see [Agent Communication](#agent-communication)). + +**Example** + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ + message: z.string().min(1), + userId: z.string().optional(), + }), + output: z.object({ + response: z.string(), + timestamp: z.number(), + }), + }, + metadata: { + name: 'greeting-agent', + description: 'A simple greeting agent that responds to user messages', + }, + handler: async (ctx, input) => { + // Input is automatically validated and typed + ctx.logger.info(`Processing message from user: ${input.userId ?? 'anonymous'}`); + + return { + response: `Hello! You said: ${input.message}`, + timestamp: Date.now(), + }; + }, +}); + +export default agent; +``` + +### Agent Configuration + +The agent configuration object defines the structure and behavior of your agent. + +**Schema Property** + +The `schema` property defines input and output validation using any library that implements StandardSchemaV1: + +```typescript +// Using Zod +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ name: z.string() }), + output: z.object({ greeting: z.string() }), + }, + handler: async (ctx, input) => { + return { greeting: `Hello, ${input.name}!` }; + }, +}); +``` + +```typescript +// Using Valibot +import * as v from 'valibot'; + +const agent = createAgent({ + schema: { + input: v.object({ name: v.string() }), + output: v.object({ greeting: v.string() }), + }, + handler: async (ctx, input) => { + return { greeting: `Hello, ${input.name}!` }; + }, +}); +``` + +**Metadata Property** + +Agent metadata provides information about the agent for documentation and tooling: + +```typescript +const agent = createAgent({ + schema: { + input: inputSchema, + output: outputSchema, + }, + metadata: { + name: 'data-processor', + description: 'Processes and transforms user data', + }, + handler: async (ctx, input) => { + // Agent logic + }, +}); +``` + +--- + +## Schema Validation + +The SDK includes built-in schema validation using the StandardSchemaV1 interface, providing runtime type safety and automatic validation. + +### StandardSchema Support + +The SDK supports any validation library that implements the StandardSchemaV1 interface: + +**Supported Libraries:** +- **Zod** - Most popular, recommended for new projects +- **Valibot** - Lightweight alternative with similar API +- **ArkType** - TypeScript-first validation +- Any library implementing StandardSchemaV1 + +**Why StandardSchema?** + +StandardSchemaV1 is a common interface that allows different validation libraries to work seamlessly with the SDK. This means: +- Not locked into a single validation library +- Can use the library that best fits your needs +- Future-proof as new libraries emerge + +**Example with Zod:** + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ + email: z.string().email(), + age: z.number().min(0).max(120), + preferences: z.object({ + newsletter: z.boolean(), + notifications: z.boolean(), + }).optional(), + }), + output: z.object({ + userId: z.string().uuid(), + created: z.date(), + }), + }, + handler: async (ctx, input) => { + // Input is validated before handler runs + // TypeScript knows exact shape of input + + ctx.logger.info(`Creating user: ${input.email}`); + + return { + userId: crypto.randomUUID(), + created: new Date(), + }; + }, +}); +``` + +**Validation Behavior:** + +- **Input validation**: Automatic before handler execution. If validation fails, an error is thrown and the handler is not called. +- **Output validation**: Automatic after handler execution. If validation fails, an error is thrown before returning to the caller. +- **Error messages**: Schema validation errors include detailed information about what failed and why. + +### Type Inference + +TypeScript automatically infers types from your schemas, providing full autocomplete and type checking: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ + query: z.string(), + filters: z.object({ + category: z.enum(['tech', 'business', 'sports']), + limit: z.number().default(10), + }), + }), + output: z.object({ + results: z.array(z.object({ + id: z.string(), + title: z.string(), + score: z.number(), + })), + total: z.number(), + }), + }, + handler: async (ctx, input) => { + // TypeScript knows: + // - input.query is string + // - input.filters.category is 'tech' | 'business' | 'sports' + // - input.filters.limit is number + + // Return type is also validated + return { + results: [ + { id: '1', title: 'Example', score: 0.95 }, + ], + total: 1, + }; + + // This would cause a TypeScript error: + // return { invalid: 'structure' }; + }, +}); + +// When calling the agent from another agent: +const result = await ctx.agent.searchAgent.run({ + query: 'agentic AI', + filters: { category: 'tech', limit: 5 }, +}); + +// TypeScript knows result has this shape: +// { +// results: Array<{ id: string; title: string; score: number }>; +// total: number; +// } +``` + +**Benefits of Type Inference:** +- Full IDE autocomplete for input and output +- Compile-time type checking catches errors before runtime +- No need to manually define TypeScript interfaces +- Refactoring is safer - changes to schemas update types automatically + +--- + +## Agent Handler + +The agent handler is the core function that processes inputs and produces outputs. + +### Handler Signature + +The handler signature has changed significantly from v0: + +```typescript +type AgentHandler = ( + ctx: AgentContext, + input: TInput +) => Promise | TOutput; +``` + +**Parameters** + +- `ctx`: The agent context providing access to services, logging, and agent capabilities +- `input`: The validated input data (typed according to your input schema) + +**Return Value** + +The handler should return the output data (typed according to your output schema). The return can be: +- A direct value: `return { result: 'value' }` +- A Promise: `return Promise.resolve({ result: 'value' })` +- An async function automatically returns a Promise + +**Key Differences from v0:** + +| Aspect | v0 | v1 | +|--------|-----|-----| +| **Parameters** | `(request, response, context)` | `(ctx, input)` | +| **Input access** | `await request.data.json()` | Direct `input` parameter | +| **Return pattern** | `return response.json(data)` | `return data` | +| **Validation** | Manual | Automatic via schemas | +| **Type safety** | Manual types | Auto-inferred from schemas | + +**Example Comparison:** + +```typescript +// v0 pattern +export default async (request, response, context) => { + const { name } = await request.data.json(); + context.logger.info(`Hello ${name}`); + return response.json({ greeting: `Hello, ${name}!` }); +}; +``` + +```typescript +// v1 pattern +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +export default createAgent({ + schema: { + input: z.object({ name: z.string() }), + output: z.object({ greeting: z.string() }), + }, + handler: async (ctx, input) => { + ctx.logger.info(`Hello ${input.name}`); + return { greeting: `Hello, ${input.name}!` }; + }, +}); +``` + +### Input Validation + +Input validation happens automatically before the handler executes: + +```typescript +const agent = createAgent({ + schema: { + input: z.object({ + email: z.string().email(), + age: z.number().min(18), + }), + output: z.object({ + success: z.boolean(), + }), + }, + handler: async (ctx, input) => { + // This code only runs if: + // - input.email is a valid email format + // - input.age is a number >= 18 + + ctx.logger.info(`Valid user: ${input.email}, age ${input.age}`); + + return { success: true }; + }, +}); +``` + +**Validation Errors:** + +If validation fails, the handler is not called and an error response is returned: + +```typescript +// Request with invalid input: +// { email: "not-an-email", age: 15 } + +// Results in validation error thrown before handler: +// Error: Validation failed +// - email: Invalid email format +// - age: Number must be greater than or equal to 18 +``` + +### Return Values + +Handlers return data directly rather than using response builder methods: + +**Simple Returns:** + +```typescript +const agent = createAgent({ + schema: { + input: z.object({ x: z.number(), y: z.number() }), + output: z.object({ sum: z.number() }), + }, + handler: async (ctx, input) => { + return { sum: input.x + input.y }; + }, +}); +``` + +**Async Processing:** + +```typescript +const agent = createAgent({ + schema: { + input: z.object({ userId: z.string() }), + output: z.object({ + user: z.object({ + id: z.string(), + name: z.string(), + }), + }), + }, + handler: async (ctx, input) => { + // Await async operations + const userData = await fetchUserFromDatabase(input.userId); + + // Return the result + return { + user: { + id: userData.id, + name: userData.name, + }, + }; + }, +}); +``` + +**Error Handling:** + +```typescript +const agent = createAgent({ + schema: { + input: z.object({ id: z.string() }), + output: z.object({ data: z.any() }), + }, + handler: async (ctx, input) => { + try { + const data = await riskyOperation(input.id); + return { data }; + } catch (error) { + ctx.logger.error('Operation failed', { error }); + throw new Error('Failed to process request'); + } + }, +}); +``` + +**Output Validation:** + +The return value is automatically validated against the output schema: + +```typescript +const agent = createAgent({ + schema: { + input: z.object({ value: z.number() }), + output: z.object({ + result: z.number(), + isPositive: z.boolean(), + }), + }, + handler: async (ctx, input) => { + // This would fail output validation: + // return { result: input.value }; + // Error: Missing required field 'isPositive' + + // This passes validation: + return { + result: input.value, + isPositive: input.value > 0, + }; + }, +}); +``` + +--- + +## Context API + +The `AgentContext` provides access to various capabilities and services within your agent handler, including storage APIs, logging, tracing, agent communication, and state management. + +### Context Properties + +The context object passed to your agent handler contains the following properties: + +```typescript +interface AgentContext { + // Identifiers + sessionId: string; // Unique ID for this agent execution + agentName?: string; // Name of the current agent + + // Agent System + agent: AgentRegistry; // Type-safe registry for calling other agents + current?: AgentRunner; // Reference to the current agent + parent?: AgentRunner; // Reference to parent agent (for subagents) + + // State Management + session: Session; // Session object for cross-request state + thread: Thread; // Thread object for conversation state + state: Map; // Request-scoped state storage + + // Storage Services + kv: KeyValueStorage; // Key-value storage + vector: VectorStorage; // Vector database for embeddings + objectstore: ObjectStorage; // Object/blob storage + stream: StreamStorage; // Stream storage + + // Observability + logger: Logger; // Structured logging + tracer: Tracer; // OpenTelemetry tracing + + // Lifecycle + waitUntil(promise: Promise | (() => void | Promise)): void; +} +``` + +**Key Properties Explained:** + +**Identifiers:** +- `sessionId`: Unique identifier for this agent execution. Use for tracking and correlating logs. +- `agentName`: The name of the currently executing agent. + +**Agent System:** +- `agent`: Type-safe registry for calling other agents. Access via `ctx.agent.otherAgentName.run(input)`. +- `current`: Reference to the current agent's runner interface. +- `parent`: Reference to the parent agent when this agent is a subagent. + +**State Management:** +- `session`: Persistent session object that spans multiple agent calls. +- `thread`: Thread object for managing conversation state. +- `state`: Map for storing request-scoped data that persists throughout the handler execution. + +**Example Usage:** + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ query: z.string() }), + output: z.object({ result: z.string() }), + }, + handler: async (ctx, input) => { + // Access session ID + ctx.logger.info(`Session ID: ${ctx.sessionId}`); + + // Use storage services + await ctx.kv.set('cache', 'last-query', input.query); + + // Store request-scoped data + ctx.state.set('startTime', Date.now()); + + // Call another agent + const enrichedData = await ctx.agent.enrichmentAgent.run({ text: input.query }); + + // Use session state + ctx.session.state.set('queryCount', + (ctx.session.state.get('queryCount') as number || 0) + 1 + ); + + return { result: enrichedData.output }; + }, +}); +``` + +### Background Tasks (waitUntil) + +The `waitUntil` method allows you to execute background tasks that don't need to block the immediate response to the caller. These tasks will be completed after the main response is sent. + +#### waitUntil + +`waitUntil(callback: Promise | (() => void | Promise)): void` + +Defers the execution of a background task until after the main response has been sent. + +**Parameters** + +- `callback`: A Promise, or a function that returns either void (synchronous) or a Promise (asynchronous), to be executed in the background + +**Example** + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ userId: z.string(), message: z.string() }), + output: z.object({ status: z.string(), timestamp: z.number() }), + }, + handler: async (ctx, input) => { + const responseData = { + status: 'received', + timestamp: Date.now() + }; + + // Schedule background tasks (async functions) + ctx.waitUntil(async () => { + // Log the message asynchronously + await logMessageToDatabase(input.userId, input.message); + }); + + ctx.waitUntil(async () => { + // Send notification email in the background + await sendNotificationEmail(input.userId, input.message); + }); + + // Can also use synchronous functions + ctx.waitUntil(() => { + // Update analytics synchronously + updateAnalyticsSync(input.userId, 'message_received'); + }); + + // Return immediately without waiting for background tasks + return responseData; + }, +}); +``` + +**Use Cases** + +- Logging and analytics that don't affect the user experience +- Sending notifications or emails +- Database cleanup or maintenance tasks +- Third-party API calls that don't impact the response +- Background data processing or enrichment + +--- + +## Storage APIs + +The SDK provides four storage APIs: Key-Value Storage, Vector Storage, Object Storage, and Stream Storage. All storage services are accessed through the agent context. + +### Key-Value Storage + +The Key-Value Storage API provides a simple way to store and retrieve data. It is accessed through the `ctx.kv` object. + +#### get + +`get(name: string, key: string): Promise` + +Retrieves a value from the key-value storage. + +**Parameters** + +- `name`: The name of the key-value storage +- `key`: The key to retrieve the value for + +**Return Value** + +Returns a Promise that resolves to a `DataResult` object with: +- `exists`: boolean indicating if the value was found +- `data`: the actual value of type T (only present when exists is true) +- `contentType`: the content type of the stored value + +**Example** + +```typescript +// Retrieve a value from key-value storage +const result = await ctx.kv.get<{ theme: string }>('user-preferences', 'user-123'); +if (result.exists) { + // data is only accessible when exists is true + ctx.logger.info('User preferences:', result.data); +} else { + ctx.logger.info('User preferences not found'); +} +``` + +#### set + +`set(name: string, key: string, value: ArrayBuffer | string | Json, ttl?: number): Promise` + +Stores a value in the key-value storage. + +**Parameters** + +- `name`: The name of the key-value storage +- `key`: The key to store the value under +- `value`: The value to store (can be an ArrayBuffer, string, or JSON object) +- `ttl` (optional): Time-to-live in seconds (minimum 60 seconds) + +**Return Value** + +Returns a Promise that resolves when the value has been stored. + +**Example** + +```typescript +// Store a string value +await ctx.kv.set('user-preferences', 'user-123', JSON.stringify({ theme: 'dark' })); + +// Store a JSON value +await ctx.kv.set('user-preferences', 'user-123', { theme: 'dark' }); + +// Store a binary value +const binaryData = new Uint8Array([1, 2, 3, 4]).buffer; +await ctx.kv.set('user-data', 'user-123', binaryData); + +// Store a value with TTL (expires after 1 hour) +await ctx.kv.set('session', 'user-123', 'active', { ttl: 3600 }); +``` + +#### delete + +`delete(name: string, key: string): Promise` + +Deletes a value from the key-value storage. + +**Parameters** + +- `name`: The name of the key-value storage +- `key`: The key to delete + +**Return Value** + +Returns a Promise that resolves when the value has been deleted. + +**Example** + +```typescript +// Delete a value +await ctx.kv.delete('user-preferences', 'user-123'); +``` + +### Vector Storage + +The Vector Storage API provides a way to store and search for data using vector embeddings. It is accessed through the `ctx.vector` object. + +#### upsert + +`upsert(name: string, ...documents: VectorUpsertParams[]): Promise` + +Inserts or updates vectors in the vector storage. + +**Parameters** + +- `name`: The name of the vector storage +- `documents`: One or more documents to upsert. Each document must include a unique `key` and either embeddings or text + +**Return Value** + +Returns a Promise that resolves to an array of string IDs for the upserted vectors. + +**Example** + +```typescript +// Upsert documents with text +const ids = await ctx.vector.upsert( + 'product-descriptions', + { key: 'chair-001', document: 'Ergonomic office chair with lumbar support', metadata: { category: 'furniture' } }, + { key: 'headphones-001', document: 'Wireless noise-cancelling headphones', metadata: { category: 'electronics' } } +); + +// Upsert documents with embeddings +const ids2 = await ctx.vector.upsert( + 'product-embeddings', + { key: 'embed-123', embeddings: [0.1, 0.2, 0.3, 0.4], metadata: { productId: '123' } }, + { key: 'embed-456', embeddings: [0.5, 0.6, 0.7, 0.8], metadata: { productId: '456' } } +); +``` + +#### search + +`search(name: string, params: VectorSearchParams): Promise` + +Searches for vectors in the vector storage. + +**Parameters** + +- `name`: The name of the vector storage +- `params`: Search parameters object with the following properties: + - `query` (string, required): The text query to search for. This will be converted to embeddings and used to find semantically similar documents. + - `limit` (number, optional): Maximum number of search results to return. Must be a positive integer. If not specified, the server default will be used. + - `similarity` (number, optional): Minimum similarity threshold for results (0.0-1.0). Only vectors with similarity scores greater than or equal to this value will be returned. 1.0 means exact match, 0.0 means no similarity requirement. + - `metadata` (object, optional): Metadata filters to apply to the search. Only vectors whose metadata matches all specified key-value pairs will be included in results. Must be a valid JSON object. + +**Return Value** + +Returns a Promise that resolves to an array of search results, each containing an ID, key, metadata, and similarity score. + +**Examples** + +```typescript +// Basic search with query only +const results = await ctx.vector.search('product-descriptions', { + query: 'comfortable office chair' +}); + +// Search with limit and similarity threshold +const results = await ctx.vector.search('product-descriptions', { + query: 'comfortable office chair', + limit: 5, + similarity: 0.7 +}); + +// Search with metadata filtering +const results = await ctx.vector.search('product-descriptions', { + query: 'comfortable office chair', + limit: 10, + similarity: 0.6, + metadata: { category: 'furniture', inStock: true } +}); + +// Process search results +for (const result of results) { + ctx.logger.info(`Product ID: ${result.id}, Similarity: ${result.similarity}`); + ctx.logger.info(`Key: ${result.key}`); + ctx.logger.info('Metadata:', result.metadata); +} +``` + +#### get + +`get(name: string, key: string): Promise` + +Retrieves a specific vector from the vector storage using its key. + +**Parameters** + +- `name`: The name of the vector storage +- `key`: The unique key of the vector to retrieve + +**Return Value** + +Returns a Promise that resolves to a `VectorSearchResult` object if found, or `null` if the key doesn't exist. + +**Example** + +```typescript +// Retrieve a specific vector by key +const vector = await ctx.vector.get('product-descriptions', 'chair-001'); + +if (vector) { + ctx.logger.info(`Found vector: ${vector.id}`); + ctx.logger.info(`Key: ${vector.key}`); + ctx.logger.info('Metadata:', vector.metadata); +} else { + ctx.logger.info('Vector not found'); +} +``` + +#### delete + +`delete(name: string, ...keys: string[]): Promise` + +Deletes one or more vectors from the vector storage. + +**Parameters** + +- `name`: The name of the vector storage +- `keys`: One or more keys of the vectors to delete + +**Return Value** + +Returns a Promise that resolves to the number of vectors that were deleted. + +**Examples** + +```typescript +// Delete a single vector by key +const deletedCount = await ctx.vector.delete('product-descriptions', 'chair-001'); +ctx.logger.info(`Deleted ${deletedCount} vector(s)`); + +// Delete multiple vectors in bulk +const deletedCount2 = await ctx.vector.delete('product-descriptions', 'chair-001', 'headphones-001', 'desk-002'); +ctx.logger.info(`Deleted ${deletedCount2} vector(s)`); + +// Delete with array spread +const keysToDelete = ['chair-001', 'headphones-001', 'desk-002']; +const deletedCount3 = await ctx.vector.delete('product-descriptions', ...keysToDelete); + +// Handle cases where some vectors might not exist +const deletedCount4 = await ctx.vector.delete('product-descriptions', 'existing-key', 'non-existent-key'); +ctx.logger.info(`Deleted ${deletedCount4} vector(s)`); // May be less than number of keys provided +``` + +### Object Storage + +The Object Storage API provides a way to store and retrieve objects (files, documents, media) with support for public URL generation. It is accessed through the `ctx.objectstore` object. + +#### get + +`get(bucket: string, key: string): Promise` + +Retrieves an object from the object storage. + +**Parameters** + +- `bucket`: The bucket to get the object from +- `key`: The key of the object to get + +**Return Value** + +Returns a Promise that resolves to an `ObjectResult` object with: +- `exists`: boolean indicating if the object was found +- `data`: Uint8Array containing the raw object data (only present when exists is true) +- `contentType`: the content type of the stored object + +**Example** + +```typescript +// Retrieve an object from object storage +const result = await ctx.objectstore.get('user-uploads', 'profile-123.jpg'); +if (result.exists) { + // data is Uint8Array directly + ctx.logger.info(`Image size: ${result.data.byteLength} bytes`); +} else { + ctx.logger.info('Image not found'); +} +``` + +#### put + +`put(bucket: string, key: string, data: DataType, params?: ObjectStorePutParams): Promise` + +Stores an object in the object storage. + +**Parameters** + +- `bucket`: The bucket to put the object into +- `key`: The key of the object to put +- `data`: The data to store (can be ArrayBuffer, string, or other DataType) +- `params` (optional): Additional parameters for the object + - `contentType`: Content type of the object (auto-detected if not provided) + - `contentEncoding`: Content encoding (e.g., 'gzip') + - `cacheControl`: Cache control header + - `contentDisposition`: Content disposition header + - `contentLanguage`: Content language + - `metadata`: Dictionary of key-value pairs for custom metadata + +**Example** + +```typescript +// Store a text file +await ctx.objectstore.put('documents', 'readme.txt', 'Hello, world!', { + contentType: 'text/plain' +}); + +// Store an object (auto-detected as application/json) +const userData = { name: 'John', age: 30 }; +await ctx.objectstore.put('users', 'user-123.json', userData); + +// Store with metadata +await ctx.objectstore.put('uploads', 'document.pdf', pdfData, { + contentType: 'application/pdf', + contentDisposition: 'attachment; filename="report.pdf"', + metadata: { + 'user-id': '12345', + 'upload-source': 'web-app' + } +}); +``` + +#### delete + +`delete(bucket: string, key: string): Promise` + +Deletes an object from the object storage. + +**Parameters** + +- `bucket`: The bucket to delete the object from +- `key`: The key of the object to delete + +**Return Value** + +Returns a Promise that resolves to a boolean indicating whether the object was deleted (true) or didn't exist (false). + +**Example** + +```typescript +const wasDeleted = await ctx.objectstore.delete('user-uploads', 'old-file.pdf'); +if (wasDeleted) { + ctx.logger.info('File deleted successfully'); +} else { + ctx.logger.info('File did not exist'); +} +``` + +#### createPublicURL + +`createPublicURL(bucket: string, key: string, expiresDuration?: number): Promise` + +Creates a time-limited public URL for accessing an object. + +**Parameters** + +- `bucket`: The bucket containing the object +- `key`: The key of the object +- `expiresDuration` (optional): Duration in milliseconds until the URL expires (defaults to 1 hour, minimum 1 minute) + +**Return Value** + +Returns a Promise that resolves to a public URL string. + +**Example** + +```typescript +// Create a public URL that expires in 1 hour +const publicUrl = await ctx.objectstore.createPublicURL( + 'user-uploads', + 'document.pdf', + 3600000 +); + +ctx.logger.info(`Download link: ${publicUrl}`); +``` + +### Stream Storage + +The Stream Storage API provides first-class support for creating and managing server-side streams. Streams are accessible via the `ctx.stream` object. + +#### create + +`create(name: string, props?: StreamCreateProps): Promise` + +Creates a new, named, writable stream. + +**Parameters** + +- `name`: A string identifier for the stream +- `props` (optional): Configuration object + - `metadata`: Key-value pairs for identifying and searching streams + - `contentType`: Content type of the stream (defaults to `application/octet-stream`) + - `compress`: Enable automatic gzip compression (defaults to `false`) + +**Return Value** + +Returns a Promise that resolves to a `Stream` object with `id`, `url`, and WritableStream methods. + +**Stream Characteristics:** +- **Read-Many**: Multiple consumers can read simultaneously +- **Re-readable**: Can be read multiple times from the beginning +- **Resumable**: Supports HTTP Range requests +- **Persistent**: URLs remain accessible until expiration + +**Example** + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ userId: z.string() }), + output: z.object({ streamId: z.string(), streamUrl: z.string() }), + }, + handler: async (ctx, input) => { + // Create a stream with metadata + const stream = await ctx.stream.create('user-export', { + contentType: 'text/csv', + metadata: { + userId: input.userId, + timestamp: Date.now() + } + }); + + // Write data in the background + ctx.waitUntil(async () => { + try { + await stream.write('Name,Email\n'); + await stream.write('John,john@example.com\n'); + } finally { + await stream.close(); + } + }); + + return { + streamId: stream.id, + streamUrl: stream.url + }; + }, +}); +``` + +#### list + +`list(params?: ListStreamsParams): Promise` + +Lists and searches streams with filtering and pagination. + +**Parameters** + +- `params` (optional): + - `name`: Filter by stream name + - `metadata`: Filter by metadata key-value pairs + - `limit`: Maximum streams to return (1-1000, default 100) + - `offset`: Number of streams to skip + +**Return Value** + +Returns a `ListStreamsResponse` with `success`, `streams` array, and `total` count. + +**Example** + +```typescript +// List all streams +const result = await ctx.stream.list(); +ctx.logger.info(`Found ${result.total} streams`); + +// Filter by metadata +const userStreams = await ctx.stream.list({ + metadata: { userId: 'user-123' } +}); +``` + +#### delete + +`delete(id: string): Promise` + +Deletes a stream by its ID. + +**Parameters** + +- `id`: The stream ID to delete + +**Example** + +```typescript +await ctx.stream.delete(streamId); +ctx.logger.info('Stream deleted successfully'); +``` + +**Example** + +```typescript +await ctx.stream.delete(streamId); +ctx.logger.info('Stream deleted successfully'); +``` + +--- + +## Logging + +The SDK provides logging functionality through the `ctx.logger` object. + +### Logger Interface + +The `Logger` interface defines the following methods: + +```typescript +interface Logger { + trace(message: unknown, ...args: unknown[]): void; + debug(message: unknown, ...args: unknown[]): void; + info(message: unknown, ...args: unknown[]): void; + warn(message: unknown, ...args: unknown[]): void; + error(message: unknown, ...args: unknown[]): void; + fatal(message: unknown, ...args: unknown[]): never; + child(opts: Record): Logger; +} +``` + +### Logging Methods + +#### trace + +`trace(message: unknown, ...args: unknown[]): void` + +Logs a trace message (most verbose logging level). + +**Parameters** + +- `message`: The message to log (can be any type) +- `args`: Additional arguments to include in the log + +**Example** + +```typescript +ctx.logger.trace('Detailed diagnostic info', { data: complexObject }); +``` + +#### debug + +`debug(message: unknown, ...args: unknown[]): void` + +Logs a debug message. + +**Parameters** + +- `message`: The message to log (can be any type) +- `args`: Additional arguments to include in the log + +**Example** + +```typescript +ctx.logger.debug('Processing request', { requestId: '123' }); +``` + +#### info + +`info(message: unknown, ...args: unknown[]): void` + +Logs an informational message. + +**Parameters** + +- `message`: The message to log (can be any type) +- `args`: Additional arguments to include in the log + +**Example** + +```typescript +ctx.logger.info('Request processed successfully', { requestId: '123' }); +``` + +#### warn + +`warn(message: unknown, ...args: unknown[]): void` + +Logs a warning message. + +**Parameters** + +- `message`: The message to log (can be any type) +- `args`: Additional arguments to include in the log + +**Example** + +```typescript +ctx.logger.warn('Resource not found', { resourceId: '456' }); +``` + +#### error + +`error(message: unknown, ...args: unknown[]): void` + +Logs an error message. + +**Parameters** + +- `message`: The message to log (can be any type) +- `args`: Additional arguments to include in the log + +**Example** + +```typescript +ctx.logger.error('Failed to process request', error); +``` + +#### fatal + +`fatal(message: unknown, ...args: unknown[]): never` + +Logs a fatal error message and exits the process. + +**Parameters** + +- `message`: The message to log (can be any type) +- `args`: Additional arguments to include in the log + +**Return Value** + +This method never returns (type `never`) as it terminates the process. + +**Example** + +```typescript +// Log fatal error and exit +ctx.logger.fatal('Critical system failure', { error, context }); +// Code after this line will never execute +``` + +**Note**: Use `fatal()` only for unrecoverable errors that require process termination. For recoverable errors, use `error()` instead. + +### Creating Child Loggers + +#### child + +`child(opts: Record): Logger` + +Creates a child logger with additional context. + +**Parameters** + +- `opts`: Additional context to include in all logs from the child logger (key-value pairs of any type) + +**Return Value** + +Returns a new `Logger` instance with the additional context. + +**Example** + +```typescript +const requestLogger = ctx.logger.child({ requestId: '123', userId: '456' }); +requestLogger.info('Processing request'); +``` + +--- + +## Telemetry + +The SDK integrates with OpenTelemetry for tracing and metrics. + +### Tracing + +The SDK provides access to OpenTelemetry tracing through the `ctx.tracer` object. + +**Example** + +```typescript +// Create a span +ctx.tracer.startActiveSpan('process-data', async (span) => { + try { + // Add attributes to the span + span.setAttribute('userId', '123'); + + // Perform some work + const result = await processData(); + + // Add events to the span + span.addEvent('data-processed', { itemCount: result.length }); + + return result; + } catch (error) { + // Record the error + span.recordException(error); + span.setStatus({ code: SpanStatusCode.ERROR }); + throw error; + } finally { + span.end(); + } +}); +``` + +--- + +## Agent Communication + +Agents can communicate with each other through a type-safe registry pattern, enabling complex multi-agent workflows with full TypeScript support. + +### Agent Registry + +The agent registry provides type-safe access to other agents in your project through the `ctx.agent` property. + +**Key Features:** +- **Type-safe calls**: TypeScript infers input and output types from agent schemas +- **Automatic validation**: Input and output are validated against schemas +- **IDE autocomplete**: Full IntelliSense support for agent names and parameters +- **Error handling**: Type-safe error responses + +**Basic Pattern:** + +```typescript +// Call another agent +const result = await ctx.agent.otherAgentName.run(input); +``` + +The agent registry is automatically generated based on your project's agents, providing compile-time type checking and runtime validation. + +### Calling Other Agents + +Agents can call other agents by accessing them through the context's agent registry. + +**Example: Simple Agent Call** + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +// First agent: enriches data +const enrichmentAgent = createAgent({ + schema: { + input: z.object({ text: z.string() }), + output: z.object({ + text: z.string(), + sentiment: z.enum(['positive', 'negative', 'neutral']), + keywords: z.array(z.string()) + }), + }, + handler: async (ctx, input) => { + // Enrichment logic + return { + text: input.text, + sentiment: 'positive', + keywords: ['example', 'keywords'] + }; + }, +}); + +// Second agent: uses enrichment agent +const processorAgent = createAgent({ + schema: { + input: z.object({ userInput: z.string() }), + output: z.object({ + processed: z.boolean(), + analysis: z.object({ + sentiment: z.string(), + keywords: z.array(z.string()) + }) + }), + }, + handler: async (ctx, input) => { + // Call the enrichment agent + const enriched = await ctx.agent.enrichmentAgent.run({ + text: input.userInput + }); + + // TypeScript knows enriched has the shape: + // { text: string; sentiment: 'positive' | 'negative' | 'neutral'; keywords: string[] } + + return { + processed: true, + analysis: { + sentiment: enriched.sentiment, + keywords: enriched.keywords + } + }; + }, +}); +``` + +**Example: Calling Multiple Agents** + +```typescript +const coordinatorAgent = createAgent({ + schema: { + input: z.object({ query: z.string() }), + output: z.object({ + results: z.array(z.any()), + metadata: z.object({ + sources: z.number(), + processingTime: z.number() + }) + }), + }, + handler: async (ctx, input) => { + const startTime = Date.now(); + + // Call multiple agents in parallel + const [webResults, dbResults, cacheResults] = await Promise.all([ + ctx.agent.webSearchAgent.run({ query: input.query }), + ctx.agent.databaseAgent.run({ query: input.query }), + ctx.agent.cacheAgent.run({ key: input.query }) + ]); + + // Combine results + const allResults = [ + ...webResults.items, + ...dbResults.records, + ...(cacheResults.found ? [cacheResults.data] : []) + ]; + + return { + results: allResults, + metadata: { + sources: 3, + processingTime: Date.now() - startTime + } + }; + }, +}); +``` + +**Error Handling:** + +```typescript +const robustAgent = createAgent({ + schema: { + input: z.object({ userId: z.string() }), + output: z.object({ success: z.boolean(), data: z.any() }), + }, + handler: async (ctx, input) => { + try { + // Try calling external agent + const result = await ctx.agent.externalService.run({ + userId: input.userId + }); + + return { success: true, data: result }; + } catch (error) { + ctx.logger.error('External service failed', { error }); + + // Fallback to cached data + const cached = await ctx.kv.get('user-cache', input.userId); + + if (cached.exists) { + return { success: true, data: cached.data }; + } + + return { success: false, data: null }; + } + }, +}); +``` + +### Parent and Child Agents + +Agents can have parent-child relationships, enabling hierarchical workflows and subagent patterns. + +**Accessing Parent Agent:** + +When an agent is called by another agent, it can access its parent through `ctx.parent`: + +```typescript +const childAgent = createAgent({ + schema: { + input: z.object({ task: z.string() }), + output: z.object({ completed: z.boolean() }), + }, + handler: async (ctx, input) => { + // Check if this agent was called by another agent + if (ctx.parent) { + ctx.logger.info(`Called by parent agent: ${ctx.parent.metadata.name}`); + + // Can access parent information but cannot call parent + // (to prevent circular calls) + } + + // Process the task + return { completed: true }; + }, +}); +``` + +**Current Agent Reference:** + +Access the current agent's metadata through `ctx.current`: + +```typescript +const selfAwareAgent = createAgent({ + metadata: { + name: 'self-aware-agent', + description: 'Knows its own identity' + }, + schema: { + input: z.object({ query: z.string() }), + output: z.object({ response: z.string() }), + }, + handler: async (ctx, input) => { + // Access current agent's metadata + const myName = ctx.current?.metadata.name; + + ctx.logger.info(`I am ${myName}, processing: ${input.query}`); + + return { + response: `Processed by ${myName}` + }; + }, +}); +``` + +**Subagent Pattern Example:** + +```typescript +// Manager agent that delegates work +const managerAgent = createAgent({ + schema: { + input: z.object({ + tasks: z.array(z.object({ + type: z.enum(['analyze', 'transform', 'validate']), + data: z.any() + })) + }), + output: z.object({ + results: z.array(z.any()), + tasksCompleted: z.number() + }), + }, + handler: async (ctx, input) => { + const results = []; + + for (const task of input.tasks) { + // Delegate to appropriate subagent based on task type + switch (task.type) { + case 'analyze': + const analyzed = await ctx.agent.analyzerAgent.run({ data: task.data }); + results.push(analyzed); + break; + case 'transform': + const transformed = await ctx.agent.transformerAgent.run({ data: task.data }); + results.push(transformed); + break; + case 'validate': + const validated = await ctx.agent.validatorAgent.run({ data: task.data }); + results.push(validated); + break; + } + } + + return { + results, + tasksCompleted: input.tasks.length + }; + }, +}); +``` + +--- + +## Router & Routes + +The SDK provides a Hono-based routing system for creating HTTP endpoints alongside your agents. Routes are defined in `route.ts` files and provide full control over HTTP request handling. + +### Creating Routes + +Routes are created using the `createRouter()` function from `@agentuity/server`. + +**Basic Setup:** + +```typescript +// src/agents/my-agent/route.ts +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +// Define routes +router.get('/', (c) => { + return c.json({ message: 'Hello from route' }); +}); + +export default router; +``` + +### HTTP Methods + +The router supports all standard HTTP methods. + +**GET Requests:** + +```typescript +router.get('/users', (c) => { + return c.json({ users: [] }); +}); + +router.get('/users/:id', (c) => { + const id = c.req.param('id'); + return c.json({ userId: id }); +}); +``` + +**POST Requests:** + +```typescript +router.post('/users', async (c) => { + const body = await c.req.json(); + return c.json({ created: true, user: body }); +}); +``` + +**PUT, PATCH, DELETE:** + +```typescript +router.put('/users/:id', async (c) => { + const id = c.req.param('id'); + const body = await c.req.json(); + return c.json({ updated: true, id, data: body }); +}); + +router.patch('/users/:id', async (c) => { + const id = c.req.param('id'); + const updates = await c.req.json(); + return c.json({ patched: true, id, updates }); +}); + +router.delete('/users/:id', (c) => { + const id = c.req.param('id'); + return c.json({ deleted: true, id }); +}); +``` + +**Calling Agents from Routes:** + +Routes can call agents through the context: + +```typescript +router.post('/process', async (c) => { + const input = await c.req.json(); + + // Call an agent + const result = await c.agent.processorAgent.run({ + data: input.data + }); + + return c.json(result); +}); +``` + +### Specialized Routes + +The router provides specialized route handlers for non-HTTP triggers like email, WebSockets, scheduled jobs, and real-time communication. + +#### Email Routes + +`router.email(address: string, handler: EmailHandler): Router` + +Handle incoming emails sent to a specific address. + +**Parameters:** + +- `address`: Email address to handle (e.g., 'support@example.com') +- `handler`: Function that receives the parsed email object and context + +**Handler Signature:** + +```typescript +type EmailHandler = (email: Email, c: Context) => any | Promise; + +interface Email { + from: string; + to: string[]; + subject: string; + text?: string; + html?: string; + headers: Record; + attachments?: Array<{ + filename: string; + contentType: string; + content: Buffer; + }>; +} +``` + +**Example:** + +```typescript +router.email('support@example.com', async (email, c) => { + c.logger.info('Email received', { + from: email.from, + subject: email.subject + }); + + // Process email and trigger agent + const result = await c.agent.emailProcessor.run({ + sender: email.from, + content: email.text || email.html || '' + }); + + return c.json({ processed: true }); +}); +``` + +#### WebSocket Routes + +`router.websocket(path: string, handler: WebSocketHandler): Router` + +Create a WebSocket endpoint for real-time bidirectional communication. + +**Parameters:** + +- `path`: Route path for the WebSocket endpoint +- `handler`: Function that returns a WebSocket connection setup function + +**Handler Signature:** + +```typescript +type WebSocketHandler = (c: Context) => (ws: WebSocketConnection) => void | Promise; + +interface WebSocketConnection { + onOpen(handler: (event: any) => void | Promise): void; + onMessage(handler: (event: any) => void | Promise): void; + onClose(handler: (event: any) => void | Promise): void; + send(data: string | ArrayBuffer | Uint8Array): void; +} +``` + +**Example:** + +```typescript +router.websocket('/chat', (c) => (ws) => { + ws.onOpen((event) => { + c.logger.info('WebSocket connected'); + ws.send(JSON.stringify({ type: 'connected' })); + }); + + ws.onMessage(async (event) => { + const message = JSON.parse(event.data); + + // Process message with agent + const response = await c.agent.chatAgent.run({ + message: message.text + }); + + ws.send(JSON.stringify({ type: 'response', data: response })); + }); + + ws.onClose((event) => { + c.logger.info('WebSocket disconnected'); + }); +}); +``` + +#### Server-Sent Events (SSE) + +`router.sse(path: string, handler: SSEHandler): Router` + +Create a Server-Sent Events endpoint for server-to-client streaming. + +**Parameters:** + +- `path`: Route path for the SSE endpoint +- `handler`: Function that returns an SSE stream setup function + +**Handler Signature:** + +```typescript +type SSEHandler = (c: Context) => (stream: SSEStream) => void | Promise; + +interface SSEStream { + write(data: string | number | boolean | object): Promise; + writeSSE(message: { data?: string; event?: string; id?: string }): Promise; + onAbort(handler: () => void): void; + close?(): void; +} +``` + +**Example:** + +```typescript +router.sse('/updates', (c) => async (stream) => { + // Send initial connection message + await stream.write({ type: 'connected' }); + + // Stream agent progress updates + const updates = await c.agent.longRunningAgent.run({ task: 'process' }); + + for (const update of updates) { + await stream.write({ + type: 'progress', + data: update + }); + } + + // Clean up on client disconnect + stream.onAbort(() => { + c.logger.info('Client disconnected'); + }); +}); +``` + +#### Stream Routes + +`router.stream(path: string, handler: StreamHandler): Router` + +Create an HTTP streaming endpoint for piping data streams. + +**Parameters:** + +- `path`: Route path for the stream endpoint +- `handler`: Function that returns a ReadableStream + +**Handler Signature:** + +```typescript +type StreamHandler = (c: Context) => ReadableStream | Promise>; +``` + +**Example:** + +```typescript +router.stream('/data', async (c) => { + // Create a readable stream + const stream = new ReadableStream({ + async start(controller) { + // Stream data chunks + const data = await c.agent.dataGenerator.run({ query: 'all' }); + + for (const chunk of data) { + controller.enqueue(new TextEncoder().encode(JSON.stringify(chunk) + '\n')); + } + + controller.close(); + } + }); + + return stream; +}); +``` + +#### Cron Routes + +`router.cron(schedule: string, handler: CronHandler): Router` + +Schedule a recurring job using cron syntax. + +**Parameters:** + +- `schedule`: Cron schedule expression (e.g., '0 9 * * *' for daily at 9am) +- `handler`: Function to execute on schedule + +**Handler Signature:** + +```typescript +type CronHandler = (c: Context) => any | Promise; +``` + +**Cron Schedule Format:** + +``` +┌───────────── minute (0 - 59) +│ ┌───────────── hour (0 - 23) +│ │ ┌───────────── day of month (1 - 31) +│ │ │ ┌───────────── month (1 - 12) +│ │ │ │ ┌───────────── day of week (0 - 6) (Sunday = 0) +│ │ │ │ │ +* * * * * +``` + +**Example:** + +```typescript +// Run daily at 9am +router.cron('0 9 * * *', async (c) => { + c.logger.info('Running daily report'); + + const report = await c.agent.reportGenerator.run({ + type: 'daily', + date: new Date().toISOString() + }); + + // Send report via email or store + await c.kv.set('reports', `daily-${Date.now()}`, report); + + return c.json({ success: true }); +}); + +// Run every 5 minutes +router.cron('*/5 * * * *', async (c) => { + await c.agent.healthCheck.run({}); + return c.json({ checked: true }); +}); +``` + +#### SMS Routes + +`router.sms(params: { number: string }, handler: SMSHandler): Router` + +Handle incoming SMS messages sent to a specific phone number. + +**Parameters:** + +- `params.number`: Phone number to handle (E.164 format, e.g., '+12345678900') +- `handler`: Function that processes the SMS + +**Handler Signature:** + +```typescript +type SMSHandler = (c: Context) => any | Promise; +``` + +**Example:** + +```typescript +router.sms({ number: '+12345678900' }, async (c) => { + const body = await c.req.json(); + + c.logger.info('SMS received', { + from: body.from, + message: body.text + }); + + // Process SMS with agent + const response = await c.agent.smsBot.run({ + sender: body.from, + message: body.text + }); + + return c.json({ reply: response }); +}); +``` + +### Route Parameters + +Access route parameters and query strings through the request object. + +**Path Parameters:** + +```typescript +router.get('/posts/:postId/comments/:commentId', (c) => { + const postId = c.req.param('postId'); + const commentId = c.req.param('commentId'); + + return c.json({ postId, commentId }); +}); +``` + +**Query Parameters:** + +```typescript +router.get('/search', (c) => { + const query = c.req.query('q'); + const limit = c.req.query('limit') || '10'; + const page = c.req.query('page') || '1'; + + return c.json({ + query, + limit: parseInt(limit), + page: parseInt(page) + }); +}); +``` + +**Request Headers:** + +```typescript +router.get('/protected', (c) => { + const authHeader = c.req.header('Authorization'); + + if (!authHeader) { + return c.json({ error: 'Unauthorized' }, 401); + } + + return c.json({ authorized: true }); +}); +``` + +--- + +## Session & Thread Management + +The SDK provides session and thread management for stateful agent interactions. + +### Sessions + +Sessions represent a user's interaction with your agents, persisting across multiple requests. + +**Session Interface:** + +```typescript +interface Session { + id: string; // Unique session identifier + thread: Thread; // Reference to the current thread + state: Map; // Session-scoped persistent state + + // Event listeners for session lifecycle (optional) + addEventListener( + eventName: 'completed', + callback: (eventName: 'completed', session: Session) => Promise | void + ): void; + removeEventListener( + eventName: 'completed', + callback: (eventName: 'completed', session: Session) => Promise | void + ): void; +} +``` + +**Using Session State:** + +```typescript +const agent = createAgent({ + schema: { + input: z.object({ message: z.string() }), + output: z.object({ + response: z.string(), + messageCount: z.number() + }), + }, + handler: async (ctx, input) => { + // Get current message count from session + const currentCount = (ctx.session.state.get('messageCount') as number) || 0; + const newCount = currentCount + 1; + + // Update session state + ctx.session.state.set('messageCount', newCount); + ctx.session.state.set('lastMessage', input.message); + ctx.session.state.set('lastTimestamp', Date.now()); + + return { + response: `Message received`, + messageCount: newCount + }; + }, +}); +``` + +### Threads + +Threads represent a specific conversation or workflow within a session. + +**Thread Interface:** + +```typescript +interface Thread { + id: string; // Unique thread identifier + state: Map; // Thread-scoped state + + // Event listeners for thread lifecycle (optional) + addEventListener( + eventName: 'destroyed', + callback: (eventName: 'destroyed', thread: Thread) => Promise | void + ): void; + removeEventListener( + eventName: 'destroyed', + callback: (eventName: 'destroyed', thread: Thread) => Promise | void + ): void; + destroy(): Promise; // Destroy the thread +} +``` + +**Using Thread State:** + +```typescript +const conversationAgent = createAgent({ + schema: { + input: z.object({ + message: z.string(), + userId: z.string() + }), + output: z.object({ + reply: z.string(), + context: z.array(z.string()) + }), + }, + handler: async (ctx, input) => { + // Get conversation history from thread + const history = (ctx.thread.state.get('history') as string[]) || []; + + // Add current message to history + history.push(input.message); + ctx.thread.state.set('history', history); + + // Store user info + ctx.thread.state.set('userId', input.userId); + + return { + reply: `Processed message ${history.length}`, + context: history + }; + }, +}); +``` + +### State Management + +The context provides three levels of state management: + +**1. Request State (`ctx.state`):** +- Scoped to the current request only +- Cleared after handler completes +- Use for temporary data within a single execution + +```typescript +handler: async (ctx, input) => { + ctx.state.set('startTime', Date.now()); + + // Do work... + + const duration = Date.now() - (ctx.state.get('startTime') as number); + ctx.logger.info(`Request took ${duration}ms`); +} +``` + +**2. Thread State (`ctx.thread.state`):** +- Persists across requests within the same thread +- Useful for conversation context and workflow state +- Destroyed when thread is destroyed + +```typescript +handler: async (ctx, input) => { + const step = (ctx.thread.state.get('currentStep') as number) || 1; + ctx.thread.state.set('currentStep', step + 1); +} +``` + +**3. Session State (`ctx.session.state`):** +- Persists across all threads in a session +- Useful for user preferences and long-term data +- Survives thread destruction + +```typescript +handler: async (ctx, input) => { + const totalRequests = (ctx.session.state.get('totalRequests') as number) || 0; + ctx.session.state.set('totalRequests', totalRequests + 1); +} +``` + +**Complete Example:** + +```typescript +const statefulAgent = createAgent({ + schema: { + input: z.object({ action: z.string(), data: z.any() }), + output: z.object({ + success: z.boolean(), + stats: z.object({ + requestDuration: z.number(), + threadStep: z.number(), + sessionTotal: z.number() + }) + }), + }, + handler: async (ctx, input) => { + // Request-scoped state + ctx.state.set('startTime', Date.now()); + + // Thread-scoped state (conversation flow) + const threadStep = (ctx.thread.state.get('step') as number) || 0; + ctx.thread.state.set('step', threadStep + 1); + ctx.thread.state.set('lastAction', input.action); + + // Session-scoped state (user-level) + const sessionTotal = (ctx.session.state.get('total') as number) || 0; + ctx.session.state.set('total', sessionTotal + 1); + ctx.session.state.set('lastSeen', Date.now()); + + // Process action + await processAction(input.action, input.data); + + return { + success: true, + stats: { + requestDuration: Date.now() - (ctx.state.get('startTime') as number), + threadStep: threadStep + 1, + sessionTotal: sessionTotal + 1 + } + }; + }, +}); +``` + +--- + +## Evaluations + +The SDK includes a built-in evaluation framework for assessing agent quality and performance. Evals run automatically after agent execution to validate outputs and measure quality metrics. + +### Creating Evals + +Evals are created using the `createEval()` method on an agent. + +**Eval Configuration:** + +```typescript +agent.createEval({ + metadata: { + name: string; // Eval name + description?: string; // What this eval checks + filename?: string; // Source file (auto-populated) + }, + handler: EvalFunction; // Eval logic +}); +``` + +**Basic Example:** + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ query: z.string() }), + output: z.object({ + answer: z.string(), + confidence: z.number() + }), + }, + handler: async (ctx, input) => { + // Agent logic + return { + answer: 'Response to ' + input.query, + confidence: 0.95 + }; + }, +}); + +// Add an eval to check confidence threshold +agent.createEval({ + metadata: { + name: 'confidence-check', + description: 'Ensures confidence is above 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 ? 'Confidence acceptable' : 'Confidence too low' + } + }; + }, +}); +``` + +### Eval Results + +Evals can return different result types depending on the evaluation needs. + +**Result Types:** + +```typescript +type EvalRunResult = + | { success: true; passed: boolean; metadata?: object } // Binary pass/fail + | { success: true; score: number; metadata?: object } // Numeric score (0-1) + | { success: false; error: string }; // Eval failed to run +``` + +**Binary Pass/Fail Eval:** + +```typescript +agent.createEval({ + metadata: { name: 'output-length-check' }, + handler: async (ctx, input, output) => { + const passed = output.answer.length >= 10; + + return { + success: true, + passed, + metadata: { + actualLength: output.answer.length, + minimumLength: 10 + } + }; + }, +}); +``` + +**Score-Based Eval:** + +```typescript +agent.createEval({ + metadata: { name: 'quality-score' }, + handler: async (ctx, input, output) => { + // Calculate quality score (0-1 range) + let score = 0; + + // Check various quality factors + if (output.answer.length > 20) score += 0.3; + if (output.confidence > 0.8) score += 0.4; + if (output.answer.includes(input.query)) score += 0.3; + + return { + success: true, + score, + metadata: { + factors: { + length: output.answer.length, + confidence: output.confidence, + relevance: output.answer.includes(input.query) + } + } + }; + }, +}); +``` + +**Error Handling in Evals:** + +```typescript +agent.createEval({ + metadata: { name: 'external-validation' }, + handler: async (ctx, input, output) => { + try { + // Call external validation service + const isValid = await validateWithExternalService(output); + + return { + success: true, + passed: isValid, + metadata: { validator: 'external-service' } + }; + } catch (error) { + // Eval itself failed + return { + success: false, + error: `Validation service error: ${error.message}` + }; + } + }, +}); +``` + +### Eval Execution + +Evals run automatically after agent execution completes, using the `waitUntil()` mechanism. + +**Execution Flow:** + +1. Agent handler executes and returns output +2. Output is validated against schema +3. Agent emits `completed` event +4. All evals attached to the agent run via `waitUntil()` +5. Eval results are sent to eval tracking service +6. Response is returned to caller (without waiting for evals) + +**Multiple Evals Example:** + +```typescript +const comprehensiveAgent = createAgent({ + schema: { + input: z.object({ text: z.string() }), + output: z.object({ + summary: z.string(), + sentiment: z.enum(['positive', 'negative', 'neutral']), + keywords: z.array(z.string()) + }), + }, + handler: async (ctx, input) => { + // Processing logic + return { + summary: 'Summary of ' + input.text, + sentiment: 'positive', + keywords: ['keyword1', 'keyword2'] + }; + }, +}); + +// Eval 1: Check summary length +comprehensiveAgent.createEval({ + metadata: { name: 'summary-length' }, + handler: async (ctx, input, output) => { + const passed = output.summary.length >= 20 && output.summary.length <= 200; + return { success: true, passed, metadata: { length: output.summary.length } }; + }, +}); + +// Eval 2: Check keywords count +comprehensiveAgent.createEval({ + metadata: { name: 'keywords-count' }, + handler: async (ctx, input, output) => { + const passed = output.keywords.length >= 2 && output.keywords.length <= 10; + return { success: true, passed, metadata: { count: output.keywords.length } }; + }, +}); + +// Eval 3: Overall quality score +comprehensiveAgent.createEval({ + metadata: { name: 'quality-score' }, + handler: async (ctx, input, output) => { + const summaryQuality = output.summary.length >= 50 ? 0.5 : 0.3; + const keywordQuality = output.keywords.length >= 3 ? 0.5 : 0.3; + const score = summaryQuality + keywordQuality; + + return { + success: true, + score, + metadata: { + summaryQuality, + keywordQuality + } + }; + }, +}); +``` + +**Accessing Context in Evals:** + +Evals receive the same context as the agent, enabling access to storage, logging, and other services: + +```typescript +agent.createEval({ + metadata: { name: 'logged-eval' }, + handler: async (ctx, input, output) => { + // Log eval execution + ctx.logger.info('Running eval', { + sessionId: ctx.sessionId, + input, + output + }); + + // Store eval results + await ctx.kv.set('eval-results', ctx.sessionId, { + timestamp: Date.now(), + passed: true + }); + + return { success: true, passed: true }; + }, +}); +``` + +--- + +## Event System + +The SDK provides a comprehensive event system for hooking into agent, session, and thread lifecycles. + +### Agent Events + +Agents emit events during their lifecycle that you can listen to. + +**Available Events:** + +```typescript +agent.addEventListener('started', (eventName, agent, ctx) => { + // Agent execution has started +}); + +agent.addEventListener('completed', (eventName, agent, ctx) => { + // Agent execution completed successfully + // Evals run during this event +}); + +agent.addEventListener('errored', (eventName, agent, ctx, error) => { + // Agent execution failed +}); +``` + +**Example: Logging Agent Lifecycle:** + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ task: z.string() }), + output: z.object({ result: z.string() }), + }, + handler: async (ctx, input) => { + return { result: `Completed: ${input.task}` }; + }, +}); + +// Log when agent starts +agent.addEventListener('started', (eventName, agent, ctx) => { + ctx.logger.info('Agent started', { + agentName: agent.metadata.name, + sessionId: ctx.sessionId + }); +}); + +// Log when agent completes +agent.addEventListener('completed', (eventName, agent, ctx) => { + ctx.logger.info('Agent completed', { + agentName: agent.metadata.name, + sessionId: ctx.sessionId + }); +}); + +// Log when agent errors +agent.addEventListener('errored', (eventName, agent, ctx, error) => { + ctx.logger.error('Agent errored', { + agentName: agent.metadata.name, + sessionId: ctx.sessionId, + error: error.message + }); +}); +``` + +### App Events + +The application instance emits events for agents, sessions, and threads. + +**Available App Events:** + +```typescript +// Agent lifecycle events +app.addEventListener('agent.started', (eventName, agent, ctx) => {}); +app.addEventListener('agent.completed', (eventName, agent, ctx) => {}); +app.addEventListener('agent.errored', (eventName, agent, ctx, error) => {}); + +// Session lifecycle events +app.addEventListener('session.started', (eventName, session) => {}); +app.addEventListener('session.completed', (eventName, session) => {}); + +// Thread lifecycle events +app.addEventListener('thread.created', (eventName, thread) => {}); +app.addEventListener('thread.destroyed', (eventName, thread) => {}); +``` + +**Example: Application-Wide Analytics:** + +```typescript +import { createApp } from '@agentuity/runtime'; + +const { app, server, logger } = createApp(); + +// Track agent executions +let agentExecutions = 0; + +app.addEventListener('agent.started', (eventName, agent, ctx) => { + agentExecutions++; + logger.info(`Total agent executions: ${agentExecutions}`); +}); + +// Track errors for monitoring +app.addEventListener('agent.errored', (eventName, agent, ctx, error) => { + logger.error('Agent error detected', { + agentName: agent.metadata.name, + error: error.message, + sessionId: ctx.sessionId + }); + + // Could send to error tracking service + // await sendToErrorTracker(error, agent, ctx); +}); + +// Session analytics +app.addEventListener('session.completed', (eventName, session) => { + const messageCount = session.state.get('messageCount') || 0; + logger.info('Session completed', { + sessionId: session.id, + totalMessages: messageCount + }); +}); + +// Thread cleanup +app.addEventListener('thread.destroyed', (eventName, thread) => { + logger.info('Thread destroyed', { + threadId: thread.id + }); +}); +``` + +### Event Handlers + +Event handlers can be added and removed dynamically. + +**Adding Event Listeners:** + +```typescript +// Agent-level +const handler = (eventName, agent, ctx) => { + ctx.logger.info('Agent started'); +}; + +agent.addEventListener('started', handler); + +// App-level +app.addEventListener('agent.completed', (eventName, agent, ctx) => { + ctx.logger.info(`${agent.metadata.name} completed`); +}); +``` + +**Removing Event Listeners:** + +```typescript +// Remove specific handler +agent.removeEventListener('started', handler); + +// Note: You must keep a reference to the handler function +// to remove it later +``` + +**Complete Example with Cleanup:** + +```typescript +const agent = createAgent({ + schema: { + input: z.object({ action: z.string() }), + output: z.object({ success: z.boolean() }), + }, + handler: async (ctx, input) => { + return { success: true }; + }, +}); + +// Create handlers with references for cleanup +const startedHandler = (eventName, agent, ctx) => { + ctx.logger.info('Started processing'); +}; + +const completedHandler = (eventName, agent, ctx) => { + ctx.logger.info('Completed processing'); +}; + +const erroredHandler = (eventName, agent, ctx, error) => { + ctx.logger.error('Error occurred', { error }); +}; + +// Add listeners +agent.addEventListener('started', startedHandler); +agent.addEventListener('completed', completedHandler); +agent.addEventListener('errored', erroredHandler); + +// Later, if needed, remove listeners +// agent.removeEventListener('started', startedHandler); +// agent.removeEventListener('completed', completedHandler); +// agent.removeEventListener('errored', erroredHandler); +``` + +--- + +## Migrating from v0 + +For users upgrading from v0, the key architectural changes include: + +- **Agent definition**: Function exports → `createAgent()` API +- **Handler signature**: `(request, response, context)` → `(ctx, input)` +- **Returns**: Explicit `response.json()` → Direct returns with schema validation +- **Agent communication**: `context.getAgent()` → `ctx.agent.name.run()` +- **File structure**: Single agent file → `agent.ts` + `route.ts` pattern +- **Context properties**: `runId` → `sessionId`, added `session`, `thread`, `state` +- **Package names**: `@agentuity/sdk` → `@agentuity/runtime` (or `@agentuity/server`) + +For complete migration instructions, see the [Migration Guide](/migration-guide). + +--- + +**This completes the API Reference documentation.** \ No newline at end of file diff --git a/content/v1/SDK/core-concepts.mdx b/content/v1/SDK/core-concepts.mdx new file mode 100644 index 00000000..e3ffa2ab --- /dev/null +++ b/content/v1/SDK/core-concepts.mdx @@ -0,0 +1,514 @@ +--- +title: Core Concepts +description: Learn the fundamental patterns and APIs of the Agentuity JavaScript SDK +--- + +# Core Concepts + +The Agentuity JavaScript SDK provides a structured approach to building AI agents. This guide covers the essential patterns and APIs you'll use when developing with the SDK. + +## Agent Architecture + +Agents are created using the `createAgent()` function from `@agentuity/runtime`. Each agent consists of a schema, metadata, and a handler function. + +### Basic Agent Structure + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ message: z.string() }), + output: z.object({ response: z.string() }) + }, + metadata: { + name: 'Chat Agent', + description: 'Processes chat messages' + }, + handler: async (ctx, input) => { + // Input is already validated and typed + return { response: `You said: ${input.message}` }; + } +}); +``` + +### Schema Validation + +The SDK supports the StandardSchema interface, which works with Zod, Valibot, and ArkType. Schemas provide: + +- Automatic input validation before handler execution +- Full TypeScript type inference for the handler +- Output validation to ensure type safety +- Runtime safety for production environments + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ + email: z.string().email(), + age: z.number().min(18) + }), + output: z.object({ + userId: z.string(), + verified: z.boolean() + }) + }, + handler: async (ctx, input) => { + // input.email and input.age are fully typed + // Return type is also validated + return { + userId: 'user-123', + verified: true + }; + } +}); +``` + +For type safety, it's recommended to add schemas to all agents. See the [Schema Validation](/Guides/schema-validation) guide for detailed patterns. + +### Two-File Pattern + +Each agent consists of two files in your project: + +1. **`agent.ts`** - Defines the agent logic, schemas, and metadata +2. **`route.ts`** - Defines HTTP endpoints and how to invoke the agent + +```typescript +// src/agents/chat/agent.ts +import { createAgent } from '@agentuity/runtime'; + +export const chatAgent = createAgent({ + schema: { /* ... */ }, + handler: async (ctx, input) => { /* ... */ } +}); +``` + +```typescript +// src/agents/chat/route.ts +import { createRouter } from '@agentuity/runtime'; +import { chatAgent } from './agent'; + +const router = createRouter(); + +router.post('/chat', async (c) => { + const input = await c.req.json(); + const result = await c.agent.chatAgent.run(input); + return c.json(result); +}); + +export default router; +``` + +The bundler auto-discovers both files based on the directory structure. For more details on project structure, see the [Architecture](/Introduction/architecture) guide. + +### Handler Signature + +Agent handlers receive two parameters: + +- **`ctx`** - The agent context object with access to all SDK capabilities +- **`input`** - The validated input data (typed based on your schema) + +Handlers return data directly. The SDK handles serialization and response formatting. + +```typescript +handler: async (ctx, input) => { + // Access context capabilities + ctx.logger.info('Processing request', { input }); + + // Return data directly (no response object needed) + return { success: true, data: processedData }; +} +``` + +## Input and Output Handling + +The SDK provides automatic validation and type safety for inputs and outputs through schemas. + +### Input Validation + +When a schema is defined, the SDK validates input before calling your handler: + +```typescript +const agent = createAgent({ + schema: { + input: z.object({ + query: z.string().min(1), + filters: z.array(z.string()).optional() + }) + }, + handler: async (ctx, input) => { + // input is validated and typed + // TypeScript knows: input.query is string + // TypeScript knows: input.filters is string[] | undefined + return { results: [] }; + } +}); +``` + +Invalid input results in an error response before the handler executes. + +### Output Validation + +Output schemas ensure your agent returns the expected data structure: + +```typescript +const agent = createAgent({ + schema: { + output: z.object({ + status: z.enum(['success', 'error']), + data: z.any() + }) + }, + handler: async (ctx, input) => { + // Return must match output schema + return { + status: 'success', + data: { /* ... */ } + }; + } +}); +``` + +### Streaming Responses + +For streaming responses (like LLM output), set `stream: true` in the schema: + +```typescript +import { streamText } from 'ai'; +import { openai } from '@ai-sdk/openai'; + +const agent = createAgent({ + schema: { + input: z.object({ prompt: z.string() }), + stream: true + }, + handler: async (ctx, input) => { + const { textStream } = streamText({ + model: openai('gpt-5-mini'), + prompt: input.prompt + }); + + return textStream; + } +}); +``` + +See the [Schema Validation](/Guides/schema-validation) guide for advanced patterns including union types, transforms, and custom validation. + +## Agent Context (ctx) + +The context object provides access to all SDK capabilities within your handler. Properties are organized by function: + +### Identifiers + +Access unique identifiers for the current execution: + +```typescript +handler: async (ctx, input) => { + // Unique ID for this execution + const sessionId = ctx.sessionId; + + // Name of the current agent + const agentName = ctx.agentName; +} +``` + +### Agent System + +Call other agents in your project or access agent references: + +```typescript +handler: async (ctx, input) => { + // Call another agent (type-safe) + const result = await ctx.agent.enrichmentAgent.run({ + text: input.data + }); + + // Reference to current agent + const self = ctx.current; + + // Parent agent reference (in subagents) + const parent = ctx.parent; + if (parent) { + const parentResult = await parent.run(input); + } +} +``` + +Agent calls are type-safe when both agents have schemas. See [Agent Communication](#agent-communication) below for patterns. + +### State Management + +Access state at three different scopes: + +```typescript +handler: async (ctx, input) => { + // Request scope - cleared after response + ctx.state.set('startTime', Date.now()); + + // Thread scope - conversation context (1 hour lifetime) + const history = ctx.thread.state.get('messages') || []; + ctx.thread.state.set('messages', [...history, input.message]); + + // Session scope - spans threads + const userPrefs = ctx.session.state.get('preferences'); + ctx.session.state.set('lastActive', new Date()); +} +``` + +| Scope | Lifetime | Use Case | +|-------|----------|----------| +| `ctx.state` | Single request | Timing, temporary calculations | +| `ctx.thread.state` | Up to 1 hour | Conversation history | +| `ctx.session.state` | Spans threads | User preferences, settings | + +See the [Sessions and Threads](/Guides/sessions-threads) guide for detailed state management patterns. + +### Storage + +Access persistent storage systems: + +```typescript +handler: async (ctx, input) => { + // Key-value storage + await ctx.kv.set('cache', 'user-123', userData, { ttl: 3600 }); + const cached = await ctx.kv.get('cache', 'user-123'); + + // Vector storage + const results = await ctx.vector.search('products', { + query: input.searchTerm, + limit: 5, + similarity: 0.7 + }); + + // Object storage + await ctx.objectstore.put('uploads', 'file.pdf', fileData, { + contentType: 'application/pdf' + }); + + // Stream storage + const stream = await ctx.stream.create('export', { + contentType: 'text/csv' + }); +} +``` + +Each storage system provides specific capabilities: +- **Key-Value**: Fast lookups with TTL support (minimum 60 seconds) +- **Vector**: Semantic search with similarity scoring +- **Object Storage**: File storage with public URL generation +- **Stream**: Large data exports and real-time streaming + +See the [API Reference](/SDK/api-reference) for complete storage API documentation. + +### Observability + +Monitor and trace agent execution: + +```typescript +handler: async (ctx, input) => { + // Structured logging + ctx.logger.info('Processing request', { input }); + ctx.logger.error('Operation failed', { error: err }); + + // OpenTelemetry tracing + const span = ctx.tracer.startSpan('database-query'); + // ... perform operation + span.end(); +} +``` + +### Lifecycle + +Execute background tasks that continue after the response is sent: + +```typescript +handler: async (ctx, input) => { + // Process data immediately + const result = processData(input); + + // Run analytics in background + ctx.waitUntil(async () => { + await trackAnalytics(result); + await updateMetrics(ctx.sessionId); + }); + + // Response sent immediately, background tasks continue + return result; +} +``` + +Use `waitUntil()` for non-blocking operations like logging, analytics, and cleanup tasks. See the [API Reference](/SDK/api-reference) for the complete context API. + +## Routing and Triggers + +Routes are defined in `route.ts` files using the `createRouter()` function. The router is built on Hono and supports HTTP methods plus specialized routes. + +### HTTP Routes + +Standard HTTP methods are available: + +```typescript +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +router.get('/status', async (c) => { + return c.json({ status: 'ok' }); +}); + +router.post('/chat', async (c) => { + const input = await c.req.json(); + const result = await c.agent.chatAgent.run(input); + return c.json(result); +}); + +router.put('/update', async (c) => { /* ... */ }); +router.patch('/modify', async (c) => { /* ... */ }); +router.delete('/remove', async (c) => { /* ... */ }); + +export default router; +``` + +### Specialized Routes + +The router provides methods for event-driven and scheduled triggers: + +```typescript +// Email handling +router.email('support@example.com', async (c) => { + const email = c.req.email; + const result = await c.agent.emailAgent.run({ + from: email.from, + subject: email.subject, + body: email.body + }); + return c.json(result); +}); + +// Scheduled cron jobs +router.cron('0 9 * * *', async (c) => { + await c.agent.dailyReportAgent.run({ date: new Date() }); + return c.json({ scheduled: true }); +}); + +// SMS handling +router.sms({ number: '+1234567890' }, async (c) => { + const sms = c.req.sms; + const result = await c.agent.smsAgent.run({ + from: sms.from, + message: sms.body + }); + return c.json(result); +}); + +// WebSocket connections +router.websocket('/chat', { + onOpen: (c, ws) => { + console.log('Connection opened'); + }, + onMessage: async (c, ws, message) => { + const result = await c.agent.chatAgent.run({ message }); + ws.send(JSON.stringify(result)); + } +}); + +// Server-Sent Events +router.sse('/stream', async (c) => { + return c.streamSSE(async (stream) => { + for (let i = 0; i < 10; i++) { + await stream.writeSSE({ data: `Event ${i}` }); + await new Promise(resolve => setTimeout(resolve, 1000)); + } + }); +}); +``` + +Trigger configuration (cron schedules, email addresses, phone numbers) is now defined in code rather than through the UI. This provides version control and type safety for your triggers. + +For detailed routing patterns, WebSocket handling, and middleware, see the [Routing & Triggers](/Guides/routing-triggers) guide. + +## Agent Communication + +Agents can call other agents in the same project using type-safe references through the context object. + +### Calling Other Agents + +Use `ctx.agent.agentName.run()` to invoke another agent: + +```typescript +const coordinatorAgent = createAgent({ + handler: async (ctx, input) => { + // Call another agent + const enriched = await ctx.agent.enrichmentAgent.run({ + text: input.rawData + }); + + // Use the result + return { processed: enriched }; + } +}); +``` + +When both agents have schemas, the call is fully type-safe. TypeScript will validate the input type and infer the output type. + +### Sequential vs Parallel Execution + +Execute agents sequentially when each depends on the previous result: + +```typescript +handler: async (ctx, input) => { + const step1 = await ctx.agent.validator.run(input); + const step2 = await ctx.agent.enricher.run(step1); + const step3 = await ctx.agent.analyzer.run(step2); + + return step3; +} +``` + +Execute agents in parallel when operations are independent: + +```typescript +handler: async (ctx, input) => { + const [webResults, dbResults, cacheResults] = await Promise.all([ + ctx.agent.webSearch.run(input), + ctx.agent.database.run(input), + ctx.agent.cache.run(input) + ]); + + return { webResults, dbResults, cacheResults }; +} +``` + +### Subagent Communication + +Access subagents using the nested path syntax: + +```typescript +// Call a subagent from anywhere +const result = await ctx.agent.team.members.run({ + action: 'list' +}); + +// From a subagent, access the parent +const parentResult = await ctx.parent.run(input); +``` + +Subagents organize related agents into parent-child hierarchies with one level of nesting. For detailed patterns and limitations, see the [Subagents](/Guides/subagents) guide. + + +Agent calls execute within the same session context, sharing the same `sessionId` and state scopes. This enables coordinated workflows across multiple agents. + + +## Next Steps + +- [Schema Validation](/Guides/schema-validation) - Advanced schema patterns and validation +- [Routing & Triggers](/Guides/routing-triggers) - HTTP methods, WebSocket, SSE, and specialized routes +- [Sessions and Threads](/Guides/sessions-threads) - State management patterns +- [Evaluations](/Guides/evaluations) - Automated quality testing +- [Events](/Guides/events) - Lifecycle hooks and monitoring +- [API Reference](/SDK/api-reference) - Complete API documentation diff --git a/content/v1/SDK/error-handling.mdx b/content/v1/SDK/error-handling.mdx new file mode 100644 index 00000000..98d79bef --- /dev/null +++ b/content/v1/SDK/error-handling.mdx @@ -0,0 +1,626 @@ +--- +title: Error Handling +description: Learn how to handle errors effectively in the Agentuity JavaScript SDK +--- + +# Error Handling + +This guide covers error handling strategies and patterns in the Agentuity JavaScript SDK. Understanding how to handle errors ensures your agents are reliable and provide clear feedback when issues occur. + +## Error Handling Overview + +Error handling in the SDK works differently depending on where the error occurs: + +- **In agents**: Throw errors directly - the SDK catches and handles them +- **In routes**: Wrap agent calls in try-catch blocks to return HTTP responses + +The SDK automatically validates input and output against schemas, preventing invalid data from reaching your handler. + +## Agent-Level Error Handling + +Agent handlers should throw errors when something goes wrong. The SDK's error handling system catches these errors and converts them to appropriate responses. + +### Basic Error Throwing + +Throw errors directly in your handler when validation or business logic fails: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ + operation: z.enum(['create', 'update', 'delete']), + data: z.any() + }) + }, + handler: async (ctx, input) => { + // Throw errors when business logic fails + if (input.operation === 'create' && !input.data) { + throw new Error('Data is required for create operation'); + } + + // Throw errors for authorization failures + const user = await getUserById(ctx.sessionId); + if (!user.canPerform(input.operation)) { + throw new Error('Unauthorized to perform this operation'); + } + + return { success: true }; + } +}); +``` + +The SDK catches the error and returns an appropriate error response. No manual error handling is needed in the agent. + +### Error Propagation Between Agents + +When agents call other agents, errors propagate automatically: + +```typescript +const coordinatorAgent = createAgent({ + handler: async (ctx, input) => { + // If validatorAgent throws an error, it propagates here + const validation = await ctx.agent.validatorAgent.run(input); + + // If enrichmentAgent throws an error, it propagates here + const enriched = await ctx.agent.enrichmentAgent.run(validation); + + return enriched; + } +}); +``` + +This allows critical operations to *fail fast*. If validation fails, downstream operations don't execute. + +### Graceful Degradation + +For optional operations, use try-catch to handle errors gracefully: + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + let enrichedData = input.data; + + // Try optional enrichment, but continue if it fails + try { + enrichedData = await ctx.agent.enrichmentAgent.run(input); + } catch (error) { + ctx.logger.warn('Enrichment failed, using original data', { + error: error instanceof Error ? error.message : String(error) + }); + // Continue with original data + } + + // Process with enriched data (or fallback to original) + return processData(enrichedData); + } +}); +``` + +This pattern is useful for: +- Optional external API calls +- Caching operations (fall back to stored data) +- Non-critical enrichment services +- Analytics or logging operations + +### Custom Error Classes + +For structured error handling, define custom error classes: + +```typescript +class ResourceNotFoundError extends Error { + constructor(resourceType: string, resourceId: string) { + super(`${resourceType} not found: ${resourceId}`); + this.name = 'ResourceNotFoundError'; + } +} + +class AuthorizationError extends Error { + constructor(action: string) { + super(`Not authorized to perform: ${action}`); + this.name = 'AuthorizationError'; + } +} + +const agent = createAgent({ + handler: async (ctx, input) => { + const user = await getUser(input.userId); + + if (!user) { + throw new ResourceNotFoundError('User', input.userId); + } + + if (!user.hasPermission('admin')) { + throw new AuthorizationError('admin access'); + } + + return { success: true }; + } +}); +``` + +Custom error classes make it easier to handle specific error types in routes or parent agents. + +## Route-Level Error Handling + +Routes wrap agent calls in try-catch blocks to return HTTP responses with appropriate status codes. + +### Basic Pattern + +```typescript +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +router.post('/process', async (c) => { + try { + const input = await c.req.json(); + const result = await c.agent.processingAgent.run(input); + + return c.json({ + success: true, + data: result + }); + } catch (error) { + return c.json( + { + success: false, + error: error instanceof Error ? error.message : 'Unknown error' + }, + 500 + ); + } +}); + +export default router; +``` + +### HTTP Status Codes + +Use appropriate status codes to indicate the type of error: + +```typescript +router.post('/users/:id', async (c) => { + try { + const userId = c.req.param('id'); + const input = await c.req.json(); + + const result = await c.agent.userAgent.run({ + userId, + ...input + }); + + return c.json({ success: true, data: result }); + } catch (error) { + const message = error instanceof Error ? error.message : 'Unknown error'; + + // Return appropriate status codes based on error + if (message.includes('not found')) { + return c.json({ error: message }, 404); + } + + if (message.includes('Unauthorized') || message.includes('permission')) { + return c.json({ error: message }, 403); + } + + if (message.includes('validation') || message.includes('invalid')) { + return c.json({ error: message }, 400); + } + + // Default to 500 for unexpected errors + return c.json({ error: 'Internal server error' }, 500); + } +}); +``` + +### Handling Custom Error Classes + +When using custom error classes, check the error type in routes: + +```typescript +import { ResourceNotFoundError, AuthorizationError } from './errors'; + +router.post('/action', async (c) => { + try { + const result = await c.agent.actionAgent.run(await c.req.json()); + return c.json({ success: true, data: result }); + } catch (error) { + if (error instanceof ResourceNotFoundError) { + return c.json({ error: error.message }, 404); + } + + if (error instanceof AuthorizationError) { + return c.json({ error: error.message }, 403); + } + + // Handle unexpected errors + const message = error instanceof Error ? error.message : 'Unknown error'; + return c.json({ error: message }, 500); + } +}); +``` + +## Schema Validation + +The SDK automatically validates input and output against schemas before and after handler execution. + +### Input Validation + +Invalid input results in an error response before the handler executes: + +```typescript +const agent = createAgent({ + schema: { + input: z.object({ + email: z.string().email(), + age: z.number().min(18), + role: z.enum(['user', 'admin']) + }) + }, + handler: async (ctx, input) => { + // If we reach this point, input is guaranteed to be valid + // - input.email is a valid email address + // - input.age is >= 18 + // - input.role is either 'user' or 'admin' + + return { success: true }; + } +}); +``` + +When validation fails, the SDK returns an error response with details about which fields failed validation. Your handler never executes. + +### Output Validation + +The SDK also validates output to ensure type safety: + +```typescript +const agent = createAgent({ + schema: { + output: z.object({ + status: z.enum(['success', 'error']), + data: z.any().optional() + }) + }, + handler: async (ctx, input) => { + // This return value is validated + return { + status: 'success', + data: { result: 'completed' } + }; + + // This would cause a validation error: + // return { status: 'invalid' }; + } +}); +``` + +Output validation catches programming errors before they reach clients, ensuring API contracts are maintained. + +## Storage API Error Handling + +Storage operations can fail due to network issues, timeouts, or service unavailability. Use try-catch blocks to handle these errors gracefully. + +### Key-Value Storage + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + try { + const cached = await ctx.kv.get('cache', input.key); + + if (cached.exists) { + return { source: 'cache', data: cached.data }; + } + } catch (error) { + ctx.logger.warn('Cache lookup failed, falling back to database', { + error: error instanceof Error ? error.message : String(error) + }); + } + + // Fall back to database if cache fails + const data = await fetchFromDatabase(input.key); + + // Try to cache for next time (don't fail if this errors) + try { + await ctx.kv.set('cache', input.key, data, { ttl: 3600 }); + } catch (error) { + ctx.logger.warn('Failed to cache result', { error }); + } + + return { source: 'database', data }; + } +}); +``` + +### Vector Storage + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + try { + const results = await ctx.vector.search('products', { + query: input.searchTerm, + limit: 5, + similarity: 0.7 + }); + + return { results }; + } catch (error) { + ctx.logger.error('Vector search failed', { + error: error instanceof Error ? error.message : String(error) + }); + + // Fall back to keyword search + const fallbackResults = await keywordSearch(input.searchTerm); + return { results: fallbackResults, fallback: true }; + } + } +}); +``` + +### Object Storage + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + try { + await ctx.objectstore.put('uploads', input.filename, input.data, { + contentType: input.contentType + }); + + const url = await ctx.objectstore.createPublicURL( + 'uploads', + input.filename, + 3600000 // 1 hour + ); + + return { success: true, url }; + } catch (error) { + ctx.logger.error('Failed to upload file', { + filename: input.filename, + error: error instanceof Error ? error.message : String(error) + }); + + throw new Error('File upload failed. Please try again.'); + } + } +}); +``` + +## Error Monitoring + +Agents fire lifecycle events that can be used to monitor errors across your application. + +### Error Event Listener + +Listen to the `errored` event to track when agents fail: + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const agent = createAgent({ + metadata: { + name: 'Processing Agent', + description: 'Processes user data' + }, + handler: async (ctx, input) => { + // Handler logic + return { success: true }; + } +}); + +// Listen for errors +agent.addEventListener('errored', (event, agent, ctx, error) => { + // Log error with context + ctx.logger.error('Agent execution failed', { + agentName: agent.metadata.name, + sessionId: ctx.sessionId, + error: error.message, + stack: error.stack + }); + + // Send to error tracking service + trackError({ + agent: agent.metadata.name, + session: ctx.sessionId, + error: error.message + }); +}); +``` + +The `errored` event provides access to: +- The agent that failed +- The execution context +- The error that was thrown + +This enables centralized error logging, alerting, and tracking across all agents. For comprehensive event handling patterns, see the [Events](/Guides/events) guide. + +## Debugging Techniques + +### Structured Logging + +Use the logger to track execution flow and identify issues: + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + ctx.logger.info('Processing started', { input }); + + try { + const step1 = await performStep1(input); + ctx.logger.debug('Step 1 completed', { step1 }); + + const step2 = await performStep2(step1); + ctx.logger.debug('Step 2 completed', { step2 }); + + return { result: step2 }; + } catch (error) { + ctx.logger.error('Processing failed', { + error: error instanceof Error ? error.message : String(error), + stack: error instanceof Error ? error.stack : undefined + }); + + throw error; + } + } +}); +``` + +Log levels: +- **`debug`** - Detailed information for debugging +- **`info`** - General informational messages +- **`warn`** - Warning messages for recoverable issues +- **`error`** - Error messages for failures + +### OpenTelemetry Tracing + +Use tracing to debug complex workflows: + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + const span = ctx.tracer.startSpan('process-data'); + + try { + span.setAttribute('input.size', JSON.stringify(input).length); + span.addEvent('processing-started'); + + const result = await processData(input); + + span.addEvent('processing-completed', { + resultSize: JSON.stringify(result).length + }); + + return result; + } catch (error) { + span.recordException(error as Error); + throw error; + } finally { + span.end(); + } + } +}); +``` + +Tracing helps identify: +- Performance bottlenecks +- Where errors occur in complex workflows +- Dependencies between operations +- Execution time for each step + +## Best Practices + +### When to Throw vs Catch + +**Throw errors** when: +- Business logic fails (invalid state, missing data) +- Authorization checks fail +- Required external services are unavailable +- Critical operations fail + +**Catch errors** when: +- Operations are optional (enrichment, caching) +- Fallback behavior exists (cache → database) +- You want to add context before re-throwing +- Converting errors to user-friendly messages in routes + +### Consistent Error Responses + +Return errors in a consistent format across your API: + +```typescript +{ + success: false, + error: "User not found", + code: "RESOURCE_NOT_FOUND" // Optional: for programmatic error handling +} +``` + +This makes it easier for clients to handle errors predictably. + +### Clear Error Messages + +Write error messages that are actionable: + +```typescript +// Good - specific and actionable +throw new Error('Email address is required for user registration'); +throw new Error('Payment failed: insufficient funds'); +throw new Error('User not found with ID: ' + userId); + +// Bad - vague and unhelpful +throw new Error('Invalid input'); +throw new Error('Error'); +throw new Error('Something went wrong'); +``` + +Include context that helps diagnose the issue, but avoid exposing sensitive information. + +### Security Considerations + +Don't expose sensitive details in error messages: + +```typescript +// Bad - exposes database structure +throw new Error('Database query failed: SELECT * FROM users WHERE secret_token = ...'); + +// Good - generic message for external errors +throw new Error('Unable to authenticate user'); + +// Good - log details internally, return generic message +try { + await authenticateUser(token); +} catch (error) { + ctx.logger.error('Authentication failed', { + error: error.message, + token: token.substring(0, 8) + '...' // Partially redacted + }); + + throw new Error('Authentication failed'); +} +``` + +Log detailed errors internally, but return generic messages to clients. + +### Testing Error Scenarios + +Test error paths in your agents: + +```typescript +// Test validation errors +test('throws error when required field is missing', async () => { + await expect( + agent.run({ /* missing required field */ }) + ).rejects.toThrow('Required field missing'); +}); + +// Test error propagation +test('propagates errors from called agents', async () => { + // Mock subagent to throw error + mockAgent.run.mockRejectedValue(new Error('Subagent failed')); + + await expect( + coordinatorAgent.run(input) + ).rejects.toThrow('Subagent failed'); +}); + +// Test graceful degradation +test('continues with fallback when optional service fails', async () => { + mockService.fetch.mockRejectedValue(new Error('Service unavailable')); + + const result = await agent.run(input); + expect(result.fallback).toBe(true); +}); +``` + +Testing error scenarios ensures your agents handle failures gracefully. + +## Next Steps + +- [Schema Validation](/Guides/schema-validation) - Define schemas to validate input and output automatically +- [Events](/Guides/events) - Monitor agent lifecycle and handle errors with event listeners +- [Routing & Triggers](/Guides/routing-triggers) - Create routes that handle HTTP requests and responses +- [API Reference](/SDK/api-reference) - Complete API documentation for context, storage, and utilities diff --git a/content/v1/meta.json b/content/v1/meta.json new file mode 100644 index 00000000..5a4483b9 --- /dev/null +++ b/content/v1/meta.json @@ -0,0 +1,10 @@ +{ + "title": "v1 Documentation", + "pages": [ + "Introduction", + "Guides", + "SDK", + "Examples", + "migration-guide" + ] +} \ No newline at end of file diff --git a/content/v1/migration-guide.mdx b/content/v1/migration-guide.mdx new file mode 100644 index 00000000..8169f0c5 --- /dev/null +++ b/content/v1/migration-guide.mdx @@ -0,0 +1,1036 @@ +--- +title: Migrating from v0 to v1 +description: A comprehensive guide to migrating your Agentuity agents from v0 to v1 +--- + +# Migrating from v0 to v1 + +Agentuity v1 represents a significant evolution of the platform, introducing new capabilities for building AI agents with improved type safety, better developer experience, and powerful new features like evaluations and advanced routing. + +This guide will help you migrate your existing v0 agents to v1, understand the breaking changes, and take advantage of the new features. + + +**Migration Timeline:** We recommend migrating to v1 as soon as possible. v0 will continue to be supported until [DATE], but all new features and improvements will only be available in v1. + + +## What's New in v1? + +v1 introduces several major improvements: + +- **Modern Architecture**: Built on Hono, a fast and lightweight web framework +- **Type-Safe Schemas**: Built-in support for Zod, Valibot, ArkType, and other StandardSchema libraries +- **Evaluation Framework**: Automatically test and validate agent outputs +- **Event System**: Integrate with agent lifecycle events for monitoring and analytics +- **Advanced Routing**: Native support for WebSocket, SSE, email, cron, and SMS +- **Session & Thread Management**: Better conversational state handling +- **Subagents**: Organize complex agents with parent-child relationships +- **React Package**: Build UIs for your agents with `@agentuity/react` + + +**Workbench:** v0's "DevMode" is being rebranded to "Workbench", to better support agent development both locally and in production. The v0 `welcome()` function pattern for suggested prompts has been removed as part of this redesign. + + +--- + +## Breaking Changes Overview + +### High Impact Changes + +These changes require code modifications in all agents: + +1. **Handler Pattern**: Default export functions replaced with `createAgent()` +2. **Request/Response**: New pattern using direct parameters and return values +3. **Context Properties**: `runId` renamed to `sessionId`, new properties added +4. **Package Structure**: SDK split into multiple packages (`@agentuity/runtime`, `@agentuity/core`, etc.) +5. **Language Support**: v1 is TypeScript-only, optimized for Bun runtime +6. **Trigger Configuration**: Cron schedules, email addresses, and SMS numbers are now configured in code (via `router.cron()`, `router.email()`, `router.sms()`) rather than in the cloud console UI + +### Medium Impact Changes + +These changes may affect some agents: + +1. **Agent Registration**: Manual registration required with app router +2. **Trigger Types**: Additional trigger types available +3. **Service APIs**: Some method signatures updated + +### Low Impact Changes + +These are additions that don't break existing code: + +1. **Schema Validation**: Optional but recommended +2. **Evaluations**: Optional quality checking framework +3. **Event Listeners**: Optional lifecycle hooks + +--- + +## Step-by-Step Migration + +### Step 1: Update Package Dependencies + +First, update your `package.json` to use the new v1 packages: + +**v0:** +```json +{ + "dependencies": { + "@agentuity/sdk": "^0.x.x" + } +} +``` + +**v1:** +```json +{ + "dependencies": { + "@agentuity/runtime": "^1.x.x" + } +} +``` + +Then install: +```bash +npm install +``` + + +**Important**: v1 uses a monorepo structure. The primary package you'll use is `@agentuity/runtime`, which includes everything you need for agent development. + + +--- + +### Step 2: Update Agent Handler Pattern + +The most significant change is how agents are created and exported. + +#### Basic Agent Handler + +**v0:** +```typescript +import { AgentHandler } from '@agentuity/sdk'; + +const handler: AgentHandler = async (request, response, context) => { + const data = await request.data.json(); + + context.logger.info('Processing request', data); + + return response.json({ + message: 'Hello from my agent!', + data: data + }); +}; + +export default handler; +``` + +**v1:** +```typescript +import { createAgent, createApp } from '@agentuity/runtime'; + +const agent = createAgent({ + metadata: { + name: 'My Agent', + description: 'A simple agent' + }, + handler: async (ctx, input) => { + ctx.logger.info('Processing request', input); + + return { + message: 'Hello from my agent!', + data: input + }; + } +}); + +const app = createApp(); +app.router.post('/my-agent', agent.handler); + +export default app.server; +``` + +**Key Changes:** +1. Import from `@agentuity/runtime` instead of `@agentuity/sdk` +2. Use `createAgent()` instead of a plain function +3. Handler receives `(ctx, input)` instead of `(request, response, context)` +4. Return values directly instead of using `response.json()` +5. Create an `app` and register your agent with the router +6. Export `app.server` instead of the handler + +**File Structure Change:** +In v0, agents were typically single files. In v1, each agent has two files: +- **`agent.ts`** - Contains the `createAgent()` call with your handler logic +- **`route.ts`** - Contains the `createRouter()` call that defines HTTP endpoints and calls your agent + +The route file imports the agent and invokes it via `ctx.agent.name.run(input)`. This separation keeps HTTP routing concerns separate from agent business logic, making agents reusable across different routes and other agents. + +--- + +#### Agent with Schema Validation (NEW in v1) + +v1 introduces optional schema validation for type safety: + +```typescript +import { createAgent, createApp } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ + message: z.string(), + count: z.number().optional() + }), + output: z.object({ + response: z.string(), + timestamp: z.number() + }) + }, + metadata: { + name: 'Typed Agent', + description: 'An agent with type-safe inputs and outputs' + }, + handler: async (ctx, input) => { + // input is fully typed as { message: string, count?: number } + + return { + response: `Received: ${input.message}`, + timestamp: Date.now() + }; + // Return type is validated automatically + } +}); + +const app = createApp(); +app.router.post('/typed-agent', agent.handler); + +export default app.server; +``` + +**Benefits:** +- Full TypeScript autocomplete +- Runtime validation of inputs and outputs +- Automatic error handling for invalid data +- Self-documenting API contracts + +--- + +### Step 3: Update Context Usage + +The context object has several changes and additions. + +#### Context Property Changes + +**v0:** +```typescript +const handler: AgentHandler = async (request, response, context) => { + // Access run ID + const id = context.runId; + + // Access services + await context.kv.set('cache', 'key', data); + + // Access logger + context.logger.info('Message'); +}; +``` + +**v1:** +```typescript +const agent = createAgent({ + handler: async (ctx) => { + // runId renamed to sessionId + const id = ctx.sessionId; + + // Services work the same way + await ctx.kv.set('cache', 'key', data); + + // Logger unchanged + ctx.logger.info('Message'); + + // NEW: Access to all agents + const result = await ctx.agent.otherAgent.run({ input: 'data' }); + + // NEW: Current agent reference + ctx.logger.info('Current agent:', { name: ctx.current?.metadata.name }); + + // NEW: State management + ctx.state.set('myKey', 'myValue'); + + // NEW: Session and thread objects + ctx.logger.info('Session:', ctx.session); + ctx.logger.info('Thread:', ctx.thread); + } +}); +``` + +**Key Changes:** +- `context.runId` → `ctx.sessionId` +- New `ctx.agent` for accessing other agents +- New `ctx.current` for current agent metadata +- New `ctx.parent` for parent agent (in subagents) +- New `ctx.state` for temporary state storage +- New `ctx.session` and `ctx.thread` for conversation management + +--- + +### Step 4: Update Request Handling + +Request handling is simplified in v1. + +#### Accessing Request Data + +**v0:** +```typescript +const handler: AgentHandler = async (request, response, context) => { + // Get JSON data + const data = await request.data.json(); + + // Get text data + const text = await request.data.text(); + + // Get binary data + const binary = await request.data.binary(); + + // Get metadata + const userId = request.get('userId'); + const trigger = request.trigger; +}; +``` + +**v1 (HTTP Routes):** +```typescript +const app = createApp(); + +app.router.post('/my-agent', async (ctx) => { + // Get JSON body directly from Hono context + const data = await ctx.req.json(); + + // Get text body + const text = await ctx.req.text(); + + // Get headers + const userId = ctx.req.header('x-user-id'); + + // Get query params + const param = ctx.req.query('param'); + + return { processed: data }; +}); +``` + +**v1 (Agent with Schema):** +```typescript +const agent = createAgent({ + schema: { + input: z.object({ message: z.string() }) + }, + handler: async (ctx, input) => { + // Input is automatically parsed and validated + // No need to call request.data.json() + ctx.logger.info('Input message:', { message: input.message }); + + return { response: 'ok' }; + } +}); +``` + +--- + +### Step 5: Update Response Handling + +Responses are now returned directly instead of using a response builder. + +#### Basic Responses + +**v0:** +```typescript +const handler: AgentHandler = async (request, response, context) => { + // JSON response + return response.json({ message: 'Hello' }); + + // Text response + return response.text('Hello'); + + // Binary response + return response.binary(buffer); + + // Empty response + return response.empty(); +}; +``` + +**v1:** +```typescript +const agent = createAgent({ + handler: async (ctx) => { + // JSON response - just return an object + return { message: 'Hello' }; + + // Text response - return string (set content-type manually if needed) + return 'Hello'; + + // For more control, use Hono's response helpers + return ctx.text('Hello'); + return ctx.json({ message: 'Hello' }); + return ctx.body(buffer); + + // Empty response + return ctx.body(null); + } +}); +``` + +**Key Changes:** +1. No `response` object - return values directly +2. Objects are automatically JSON-serialized +3. Use Hono context methods for advanced responses +4. For typed responses, define `outputSchema` + +--- + +### Step 6: Update Service Usage + + +**Good News**: Service APIs (KV, Vector, ObjectStore, Stream) remain largely unchanged between v0 and v1. Your existing service code should work with minimal modifications. + + +#### Key-Value Storage + +**v0:** +```typescript +// Set with TTL +await context.kv.set('cache', 'key', data, { ttl: 3600 }); + +// Get +const result = await context.kv.get('cache', 'key'); +if (result.exists) { + const value = await result.data.json(); +} + +// Delete +await context.kv.delete('cache', 'key'); +``` + +**v1:** +```typescript +// Services work the same way! +// TTL is in seconds (minimum 60) +await ctx.kv.set('cache', 'key', data, { ttl: 3600 }); + +const result = await ctx.kv.get('cache', 'key'); +if (result.exists) { + const value = await result.data.json(); +} + +await ctx.kv.delete('cache', 'key'); +``` + +--- + +### Step 7: Update Agent-to-Agent Communication + +Agent-to-agent communication has been simplified and made type-safe in v1. + +**v0:** +```typescript +const handler: AgentHandler = async (request, response, context) => { + // Get agent by ID + const agent = await context.getAgent({ id: 'agent_123' }); + + // Or by name + const agent = await context.getAgent({ + name: 'other-agent', + projectId: 'proj_123' + }); + + // Run the agent + const result = await agent.run({ + data: JSON.stringify({ message: 'Hello' }), + contentType: 'application/json' + }); + + const output = await result.data.json(); +}; +``` + +**v1:** +```typescript +const agent = createAgent({ + handler: async (ctx) => { + // Access agents directly by name - much simpler! + const result = await ctx.agent.otherAgent.run({ + message: 'Hello' + }); + + // Result is automatically typed if the agent has a schema + ctx.logger.info('Agent response:', { response: result.response }); + + return result; + } +}); +``` + +**Key Changes:** +1. `ctx.agent.otherAgent.run()` instead of `getAgent()` +2. No need to JSON-stringify data +3. Type-safe when using schemas +4. Subagents accessible via `ctx.agent.parent.child.run()` + +--- + +## Advanced Migration Topics + +Now that you've covered the essential migration steps, the following sections explore advanced features and patterns available in v1. These are optional enhancements that can improve your agents' capabilities, but aren't required for basic functionality. + +### Migrating to Advanced Routing + +v1 introduces specialized route types for different use cases. + +#### Email Routes + +**v0 (Webhook approach):** +```typescript +const handler: AgentHandler = async (request, response, context) => { + // Parse email manually + const emailData = await request.data.text(); + // ... manual RFC822 parsing +}; +``` + +**v1 (Native email route):** +```typescript +const app = createApp(); + +app.router.email('support@example.com', async (email, ctx) => { + // Email is automatically parsed + ctx.logger.info('Email received:', { + from: email.fromEmail(), + subject: email.subject() + }); + + // Access email properties + const textBody = email.text(); + const htmlBody = email.html(); + const attachments = email.attachments(); + + // TODO: Email reply functionality is not yet available in v1 + + return { status: 'processed' }; +}); +``` + +#### WebSocket Routes (NEW) + +```typescript +app.router.websocket('/chat', (ctx) => (ws) => { + ws.onOpen((event) => { + ctx.logger.info('Client connected'); + ws.send('Welcome!'); + }); + + ws.onMessage(async (event) => { + const message = event.data; + ctx.logger.info('Received:', message); + + // Process with agent logic + const response = await processMessage(message); + ws.send(response); + }); + + ws.onClose((event) => { + ctx.logger.info('Client disconnected'); + }); +}); +``` + +#### Server-Sent Events (NEW) + +```typescript +app.router.sse('/updates', (ctx) => async (stream) => { + // Send updates to client + await stream.write({ event: 'started', data: 'Processing...' }); + + // Do work + const result = await longRunningTask(); + + await stream.write({ event: 'progress', data: '50%' }); + + // Final result + await stream.write({ event: 'complete', data: result }); +}); +``` + +#### Cron Routes (NEW) + +```typescript +app.router.cron('0 0 * * *', async (ctx) => { + // Runs daily at midnight + ctx.logger.info('Running daily job'); + + const report = await generateDailyReport(); + await ctx.kv.set('reports', 'daily-latest', report); + + return { status: 'completed' }; +}); +``` + +--- + +### Adding Evaluations (NEW in v1) + +Evaluations allow you to automatically test agent outputs for quality, accuracy, or compliance. + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ question: z.string() }), + output: z.object({ answer: z.string() }) + }, + handler: async (ctx, input) => { + const answer = await generateAnswer(input.question); + return { answer }; + } +}); + +// Add an evaluation +agent.createEval({ + metadata: { + name: 'answer-quality', + description: 'Checks if answer is relevant and complete' + }, + handler: async (ctx, input, output) => { + // Evaluate the output + const isRelevant = await checkRelevance(input.question, output.answer); + const isComplete = output.answer.length > 50; + + if (isRelevant && isComplete) { + return { + success: true, + score: 0.95, + metadata: { + relevant: isRelevant, + complete: isComplete + } + }; + } else { + return { + success: true, + score: 0.5, + metadata: { + relevant: isRelevant, + complete: isComplete + } + }; + } + } +}); +``` + +**When to use evaluations:** +- Quality checking (accuracy, relevance, completeness) +- Compliance validation (PII detection, content policy) +- Performance monitoring (response time, token usage) +- A/B testing (comparing different approaches) + +--- + +### Using Event Listeners (NEW in v1) + +Integrate with agent lifecycle events for monitoring and analytics. + +```typescript +import { createAgent, createApp } from '@agentuity/runtime'; + +const agent = createAgent({ + handler: async (ctx, input) => { + return { result: 'success' }; + } +}); + +// Agent-level events +agent.addEventListener('started', async (event, agent, ctx) => { + ctx.logger.info('Agent started', { agentName: agent.metadata.name }); +}); + +agent.addEventListener('completed', async (event, agent, ctx) => { + ctx.logger.info('Agent completed', { + agentName: agent.metadata.name, + sessionId: ctx.sessionId + }); +}); + +agent.addEventListener('errored', async (event, agent, ctx, error) => { + ctx.logger.error('Agent errored', { + agentName: agent.metadata.name, + error: error.message + }); +}); + +// App-level events +const app = createApp(); + +app.addEventListener('agent.started', async (agent, ctx) => { + // Track all agent starts + await analytics.track('agent_started', { + agent: agent.metadata.name, + session: ctx.sessionId + }); +}); + +app.addEventListener('agent.completed', async (agent, ctx) => { + // Track all agent completions + await analytics.track('agent_completed', { + agent: agent.metadata.name, + session: ctx.sessionId + }); +}); +``` + +--- + +### Working with Sessions and Threads (NEW in v1) + +v1 introduces explicit session and thread management for conversational agents. + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + // Access session info + ctx.logger.info('Session info:', { + sessionId: ctx.sessionId, + session: ctx.session + }); + + // Access thread for conversation history + ctx.logger.info('Thread:', ctx.thread); + + // Store conversation state + ctx.state.set('lastUserMessage', input.message); + ctx.state.set('conversationTurn', + (ctx.state.get('conversationTurn') as number || 0) + 1 + ); + + return { response: 'Message received' }; + } +}); +``` + +**Use cases:** +- Multi-turn conversations +- Maintaining context across requests +- User preference storage +- Conversation history tracking + +--- + +### Creating Subagents (NEW in v1) + +Organize complex agents with parent-child relationships. + +TODO: Add example of subagents + +**Project Structure:** +``` +src/agents/ + ├── email-agent/ + │ ├── index.ts (parent agent) + │ └── email-agent.parser/ + │ └── index.ts (subagent) +``` + +**Parent Agent:** +```typescript +// src/agents/email-agent/index.ts +import { createAgent } from '@agentuity/runtime'; + +export const emailAgent = createAgent({ + metadata: { + name: 'Email Agent', + description: 'Processes incoming emails' + }, + handler: async (ctx, input) => { + // Use subagent for parsing + const parsed = await ctx.agent.emailAgent.parser.run({ + rawEmail: input.email + }); + + // Process parsed data + return { status: 'processed', data: parsed }; + } +}); +``` + +**Subagent:** +```typescript +// src/agents/email-agent/email-agent.parser/index.ts +import { createAgent } from '@agentuity/runtime'; + +export const parserAgent = createAgent({ + metadata: { + name: 'Email Parser', + description: 'Parses email structure' + }, + handler: async (ctx, input) => { + // Access parent if needed + ctx.logger.info('Parent agent:', { parent: ctx.parent?.metadata.name }); + + // Parse email + const parsed = parseEmailStructure(input.rawEmail); + + return parsed; + } +}); +``` + +--- + +## Common Migration Patterns + +### Pattern 1: Simple REST API Agent + +**v0:** +```typescript +import { AgentHandler } from '@agentuity/sdk'; + +const handler: AgentHandler = async (request, response, context) => { + const { name } = await request.data.json(); + return response.json({ greeting: `Hello, ${name}!` }); +}; + +export default handler; +``` + +**v1:** +```typescript +import { createAgent, createApp } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ name: z.string() }), + output: z.object({ greeting: z.string() }) + }, + handler: async (ctx, input) => { + return { greeting: `Hello, ${input.name}!` }; + } +}); + +const app = createApp(); +app.router.post('/greet', agent.handler); + +export default app.server; +``` + +--- + +### Pattern 2: Agent with Storage + +**v0:** +```typescript +import { AgentHandler } from '@agentuity/sdk'; + +const handler: AgentHandler = async (request, response, context) => { + const { userId, data } = await request.data.json(); + + // Store data + await context.kv.set('user-data', userId, data); + + // Retrieve data + const result = await context.kv.get('user-data', userId); + const storedData = result.exists ? await result.data.json() : null; + + return response.json({ stored: storedData }); +}; + +export default handler; +``` + +**v1:** +```typescript +import { createAgent, createApp } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ + userId: z.string(), + data: z.record(z.unknown()) + }) + }, + handler: async (ctx, input) => { + // Store data (same API!) + await ctx.kv.set('user-data', input.userId, input.data); + + // Retrieve data (same API!) + const result = await ctx.kv.get('user-data', input.userId); + const storedData = result.exists ? await result.data.json() : null; + + return { stored: storedData }; + } +}); + +const app = createApp(); +app.router.post('/store', agent.handler); + +export default app.server; +``` + +--- + +### Pattern 3: Multi-Agent Workflow + +**v0:** +```typescript +import { AgentHandler } from '@agentuity/sdk'; + +const handler: AgentHandler = async (request, response, context) => { + const input = await request.data.json(); + + // Call first agent + const agent1 = await context.getAgent({ name: 'processor' }); + const result1 = await agent1.run({ + data: JSON.stringify(input), + contentType: 'application/json' + }); + const processed = await result1.data.json(); + + // Call second agent + const agent2 = await context.getAgent({ name: 'validator' }); + const result2 = await agent2.run({ + data: JSON.stringify(processed), + contentType: 'application/json' + }); + const validated = await result2.data.json(); + + return response.json(validated); +}; + +export default handler; +``` + +**v1:** +```typescript +import { createAgent, createApp } from '@agentuity/runtime'; + +const agent = createAgent({ + handler: async (ctx, input) => { + // Call first agent (much simpler!) + const processed = await ctx.agent.processor.run(input); + + // Call second agent + const validated = await ctx.agent.validator.run(processed); + + return validated; + } +}); + +const app = createApp(); +app.router.post('/workflow', agent.handler); + +export default app.server; +``` + +--- + +## Troubleshooting + +### Common Migration Issues + +#### Issue 1: "Cannot find module '@agentuity/sdk'" + +**Cause**: You haven't updated your imports from v0 to v1. + +**Solution**: Change all imports from: +```typescript +import { ... } from '@agentuity/sdk'; +``` + +To: +```typescript +import { ... } from '@agentuity/runtime'; +``` + +--- + +#### Issue 2: "Property 'runId' does not exist on type 'AgentContext'" + +**Cause**: `runId` was renamed to `sessionId` in v1. + +**Solution**: Replace all instances of `context.runId` with `ctx.sessionId`. + +--- + +#### Issue 3: "Handler is not a function" + +**Cause**: You're exporting the agent directly instead of the app server. + +**Solution**: Make sure you export the app server: +```typescript +const app = createApp(); +app.router.post('/agent', agent.handler); + +export default app.server; // Not 'agent' or 'handler' +``` + +--- + +#### Issue 4: "Input validation failed" + +**Cause**: You defined an input schema but the incoming data doesn't match it. + +**Solution**: Check your schema definition and ensure incoming data matches: +```typescript +const agent = createAgent({ + schema: { + input: z.object({ + message: z.string(), + // Make optional fields explicit + metadata: z.record(z.unknown()).optional() + }) + }, + handler: async (ctx, input) => { + // ... + } +}); +``` + +--- + +#### Issue 5: "Cannot access other agents" + +**Cause**: Agents aren't properly registered with the app router. + +**Solution**: Ensure all agents are registered: +```typescript +const app = createApp(); + +// Register each agent +app.router.post('/agent1', agent1.handler); +app.router.post('/agent2', agent2.handler); + +// Now they can access each other via ctx.agent +``` + +--- + +### Getting Help + +If you encounter issues not covered in this guide: + +1. **Check the Documentation**: Visit the [v1 API Reference](/SDKs/javascript/api-reference) for detailed information +2. **Review Examples**: Browse the [Examples](/Examples) section for working code +3. **Community Support**: Join our [Discord community](https://discord.gg/agentuity) for help +4. **Report Issues**: Open an issue on [GitHub](https://github.com/agentuity/sdk) if you find bugs + +--- + +## Next Steps + +After migrating your agents: + +1. **Add Schema Validation**: Improve type safety with [Schema Validation](/SDKs/javascript/schema-validation) +2. **Implement Evaluations**: Ensure quality with the [Evaluation Framework](/SDKs/javascript/evaluations) +3. **Use Advanced Routing**: Explore [WebSocket, SSE, and other routes](/Guides/routing-triggers) +4. **Add Event Listeners**: Monitor your agents with [Events](/SDKs/javascript/events) +5. **Organize with Subagents**: Structure complex agents with [Subagents](/SDKs/javascript/subagents) From 662554c4d527245788e9721b6085e58ecf1c5465 Mon Sep 17 00:00:00 2001 From: parteeksingh24 Date: Tue, 18 Nov 2025 11:20:39 -0800 Subject: [PATCH 02/63] Fix context object usage --- content/v1/Examples/index.mdx | 178 ++++---- content/v1/Guides/agent-communication.mdx | 188 ++++---- content/v1/Guides/agent-logging.mdx | 416 +++++++++++++++++ content/v1/Guides/evaluations.mdx | 118 ++--- content/v1/Guides/events.mdx | 126 +++--- content/v1/Guides/key-value-storage.mdx | 62 +-- content/v1/Guides/object-storage.mdx | 529 ++++++++++++++++++++++ content/v1/Guides/routing-triggers.mdx | 2 +- content/v1/Guides/schema-validation.mdx | 30 +- content/v1/Guides/sessions-threads.mdx | 208 ++++----- content/v1/Guides/subagents.mdx | 140 +++--- content/v1/Guides/vector-storage.mdx | 8 +- content/v1/Introduction/architecture.mdx | 18 +- content/v1/Introduction/core-concepts.mdx | 28 +- content/v1/Introduction/introduction.mdx | 2 +- content/v1/SDK/core-concepts.mdx | 112 ++--- content/v1/SDK/error-handling.mdx | 78 ++-- 17 files changed, 1594 insertions(+), 649 deletions(-) create mode 100644 content/v1/Guides/agent-logging.mdx create mode 100644 content/v1/Guides/object-storage.mdx diff --git a/content/v1/Examples/index.mdx b/content/v1/Examples/index.mdx index c57d3904..c825b901 100644 --- a/content/v1/Examples/index.mdx +++ b/content/v1/Examples/index.mdx @@ -15,8 +15,8 @@ Here's a basic agent that processes requests and returns responses: import { createAgent } from '@agentuity/runtime'; const agent = createAgent({ - handler: async (ctx, input) => { - ctx.logger.info('Received request'); + handler: async (c, input) => { + c.logger.info('Received request'); return { message: 'Hello, World!', @@ -31,7 +31,7 @@ export default agent; **Key Points:** - Direct return values (no response object needed) - Input is automatically available -- Logger accessed via `ctx.logger` +- Logger accessed via `c.logger` - Returns plain JavaScript objects ### Type-Safe Agent with Validation @@ -53,7 +53,7 @@ const agent = createAgent({ status: z.enum(['active', 'pending']) }) }, - handler: async (ctx, input) => { + handler: async (c, input) => { // input.email and input.age are fully typed and validated return { userId: crypto.randomUUID(), @@ -83,25 +83,25 @@ This example demonstrates structured logging for monitoring and debugging: import { createAgent } from '@agentuity/runtime'; const agent = createAgent({ - handler: async (ctx, input) => { + handler: async (c, input) => { // Different log levels - ctx.logger.info('Processing request', { - sessionId: ctx.sessionId, + c.logger.info('Processing request', { + sessionId: c.sessionId, inputSize: JSON.stringify(input).length }); - ctx.logger.debug('Detailed processing info', { data: input }); + c.logger.debug('Detailed processing info', { data: input }); try { const result = await processData(input); - ctx.logger.info('Processing successful', { + c.logger.info('Processing successful', { resultSize: JSON.stringify(result).length }); return result; } catch (error) { - ctx.logger.error('Processing failed', { + c.logger.error('Processing failed', { error: error.message, stack: error.stack }); @@ -155,11 +155,11 @@ const agent = createAgent({ success: v.boolean() }) }, - handler: async (ctx, input) => { + handler: async (c, input) => { const { action, userId, preferences } = input; if (action === 'get') { - const result = await ctx.kv.get('user-preferences', userId); + const result = await c.kv.get('user-preferences', userId); if (!result.exists) { return { success: false, message: 'No preferences found' }; @@ -170,7 +170,7 @@ const agent = createAgent({ } if (action === 'set') { - await ctx.kv.set( + await c.kv.set( 'user-preferences', userId, preferences, @@ -181,7 +181,7 @@ const agent = createAgent({ } // Delete - await ctx.kv.delete('user-preferences', userId); + await c.kv.delete('user-preferences', userId); return { success: true, message: 'Preferences deleted' }; } }); @@ -190,8 +190,8 @@ export default agent; ``` **Key Points:** -- `ctx.kv.get()` returns a result with `exists` flag -- `ctx.kv.set()` supports optional TTL for expiring data +- `c.kv.get()` returns a result with `exists` flag +- `c.kv.set()` supports optional TTL for expiring data - Data can be stored as JSON objects or strings - Storage is namespaced by the first parameter @@ -226,7 +226,7 @@ const agent = createAgent({ deletedCount: 'number?' }) }, - handler: async (ctx, input) => { + handler: async (c, input) => { const { action, query, products } = input; if (action === 'index') { @@ -245,7 +245,7 @@ const agent = createAgent({ } })); - const ids = await ctx.vector.upsert('products', ...documents); + const ids = await c.vector.upsert('products', ...documents); return { message: `Indexed ${ids.length} products`, @@ -258,7 +258,7 @@ const agent = createAgent({ throw new Error('Query is required'); } - const results = await ctx.vector.search('products', { + const results = await c.vector.search('products', { query, limit: 5, similarity: 0.7 @@ -283,7 +283,7 @@ const agent = createAgent({ } const productIds = products.map(p => p.id); - const deletedCount = await ctx.vector.delete('products', ...productIds); + const deletedCount = await c.vector.delete('products', ...productIds); return { message: `Deleted ${deletedCount} product(s)`, @@ -329,14 +329,14 @@ const agent = createAgent({ message: v.optional(v.string()) }) }, - handler: async (ctx, input) => { + handler: async (c, input) => { const { action, filename, data, expiresIn } = input; if (action === 'upload') { - await ctx.objectstore.put('documents', filename, data, { + await c.objectstore.put('documents', filename, data, { contentType: 'application/pdf', metadata: { - uploadedBy: ctx.sessionId, + uploadedBy: c.sessionId, uploadedAt: new Date().toISOString() } }); @@ -348,7 +348,7 @@ const agent = createAgent({ } if (action === 'download') { - const result = await ctx.objectstore.get('documents', filename); + const result = await c.objectstore.get('documents', filename); if (!result.exists) { return { @@ -364,7 +364,7 @@ const agent = createAgent({ } if (action === 'share') { - const url = await ctx.objectstore.createPublicURL( + const url = await c.objectstore.createPublicURL( 'documents', filename, expiresIn || 3600000 // Default 1 hour @@ -378,7 +378,7 @@ const agent = createAgent({ } // Delete - const deleted = await ctx.objectstore.delete('documents', filename); + const deleted = await c.objectstore.delete('documents', filename); return { success: deleted, @@ -404,22 +404,22 @@ Calling other agents to build workflows: import { createAgent } from '@agentuity/runtime'; const agent = createAgent({ - handler: async (ctx, input) => { - ctx.logger.info('Starting multi-agent workflow'); + handler: async (c, input) => { + c.logger.info('Starting multi-agent workflow'); // Call enrichment agent - const enriched = await ctx.agent.enrichmentAgent.run({ + const enriched = await c.agent.enrichmentAgent.run({ text: input.text }); // Call multiple agents in parallel const [analyzed, categorized] = await Promise.all([ - ctx.agent.analyzerAgent.run({ data: enriched }), - ctx.agent.categorizerAgent.run({ data: enriched }) + c.agent.analyzerAgent.run({ data: enriched }), + c.agent.categorizerAgent.run({ data: enriched }) ]); // Call final processing agent with combined results - const final = await ctx.agent.processorAgent.run({ + const final = await c.agent.processorAgent.run({ analyzed, categorized, original: input @@ -436,7 +436,7 @@ export default agent; ``` **Key Points:** -- Access agents via `ctx.agent.agentName.run()` +- Access agents via `c.agent.agentName.run()` - Type-safe when agents have schemas - Supports parallel execution with `Promise.all()` - Results automatically validated against output schemas @@ -462,9 +462,9 @@ const agent = createAgent({ confidence: z.number() }) }, - handler: async (ctx, input) => { + handler: async (c, input) => { // Search vector database for relevant context - const results = await ctx.vector.search('knowledge-base', { + const results = await c.vector.search('knowledge-base', { query: input.question, limit: 3, similarity: 0.7 @@ -550,9 +550,9 @@ const agent = createAgent({ error: z.string().optional() }) }, - handler: async (ctx, input) => { + handler: async (c, input) => { try { - ctx.logger.info(`Processing resource: ${input.resourceId}`); + c.logger.info(`Processing resource: ${input.resourceId}`); // Simulate resource lookup const resource = await lookupResource(input.resourceId, ctx); @@ -567,7 +567,7 @@ const agent = createAgent({ } catch (error) { // Handle different error types if (error instanceof ValidationError) { - ctx.logger.warn(`Validation error: ${error.message}`); + c.logger.warn(`Validation error: ${error.message}`); return { error: 'Validation error', message: error.message @@ -575,7 +575,7 @@ const agent = createAgent({ } if (error instanceof ResourceNotFoundError) { - ctx.logger.warn(`Resource not found: ${error.message}`); + c.logger.warn(`Resource not found: ${error.message}`); return { error: 'Resource not found', message: error.message @@ -583,7 +583,7 @@ const agent = createAgent({ } // Handle unexpected errors - ctx.logger.error('Unexpected error', error); + c.logger.error('Unexpected error', error); return { error: 'Internal server error', message: 'An unexpected error occurred' @@ -594,7 +594,7 @@ const agent = createAgent({ // Helper functions async function lookupResource(resourceId: string, ctx: any) { - const result = await ctx.kv.get('resources', resourceId); + const result = await c.kv.get('resources', resourceId); if (!result.exists) { throw new ResourceNotFoundError(resourceId); @@ -604,7 +604,7 @@ async function lookupResource(resourceId: string, ctx: any) { } async function processResource(resource: any, ctx: any) { - ctx.logger.debug('Processing resource', resource); + c.logger.debug('Processing resource', resource); return { id: resource.id, @@ -647,31 +647,31 @@ const agent = createAgent({ }) }) }, - handler: async (ctx, input) => { + handler: async (c, input) => { // Request-scoped state (cleared after request) - ctx.state.set('startTime', Date.now()); + c.state.set('startTime', Date.now()); // Thread-scoped state (conversation context) if (input.resetThread) { - await ctx.thread.destroy(); + await c.thread.destroy(); } - const threadMessages = (ctx.thread.state.get('messageCount') as number) || 0; - ctx.thread.state.set('messageCount', threadMessages + 1); + const threadMessages = (c.thread.state.get('messageCount') as number) || 0; + c.thread.state.set('messageCount', threadMessages + 1); - const messages = (ctx.thread.state.get('messages') as string[]) || []; + const messages = (c.thread.state.get('messages') as string[]) || []; messages.push(input.message); - ctx.thread.state.set('messages', messages); + c.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()); + const sessionTotal = (c.session.state.get('totalRequests') as number) || 0; + c.session.state.set('totalRequests', sessionTotal + 1); + c.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), + requestDuration: Date.now() - (c.state.get('startTime') as number), threadMessages: threadMessages + 1, sessionTotal: sessionTotal + 1 } @@ -701,8 +701,8 @@ const agent = createAgent({ input: z.object({ task: z.string() }), output: z.object({ result: z.string() }) }, - handler: async (ctx, input) => { - ctx.logger.info('Processing task', { task: input.task }); + handler: async (c, input) => { + c.logger.info('Processing task', { task: input.task }); // Simulate processing await new Promise(resolve => setTimeout(resolve, 100)); @@ -713,23 +713,23 @@ const agent = createAgent({ // Agent lifecycle events agent.addEventListener('started', (eventName, agent, ctx) => { - ctx.logger.info('Agent started', { + c.logger.info('Agent started', { agentName: agent.metadata.name, - sessionId: ctx.sessionId + sessionId: c.sessionId }); }); agent.addEventListener('completed', (eventName, agent, ctx) => { - ctx.logger.info('Agent completed successfully', { + c.logger.info('Agent completed successfully', { agentName: agent.metadata.name, - sessionId: ctx.sessionId + sessionId: c.sessionId }); }); agent.addEventListener('errored', (eventName, agent, ctx, error) => { - ctx.logger.error('Agent failed', { + c.logger.error('Agent failed', { agentName: agent.metadata.name, - sessionId: ctx.sessionId, + sessionId: c.sessionId, error: error.message }); }); @@ -748,14 +748,14 @@ const app = createApp(); app.addEventListener('agent.started', (eventName, agent, ctx) => { app.logger.info('Agent execution started', { agent: agent.metadata.name, - session: ctx.sessionId + session: c.sessionId }); }); app.addEventListener('agent.completed', (eventName, agent, ctx) => { app.logger.info('Agent execution completed', { agent: agent.metadata.name, - session: ctx.sessionId + session: c.sessionId }); }); @@ -790,29 +790,29 @@ const agent = createAgent({ summary: z.string() }) }, - handler: async (ctx, input) => { - ctx.logger.info(`Starting workflow: ${input.query}`); + handler: async (c, input) => { + c.logger.info(`Starting workflow: ${input.query}`); // Step 1: Search for relevant information - const searchResults = await ctx.agent.searchAgent.run({ + const searchResults = await c.agent.searchAgent.run({ query: input.query, limit: 10 }); // Step 2: Analyze results (conditional based on depth) const analysis = input.analysisDepth === 'deep' - ? await ctx.agent.deepAnalyzer.run({ + ? await c.agent.deepAnalyzer.run({ data: searchResults, includeDetails: true }) - : await ctx.agent.basicAnalyzer.run({ + : await c.agent.basicAnalyzer.run({ data: searchResults }); // Step 3: Generate recommendations (optional) let recommendations; if (input.includeRecommendations) { - const recResponse = await ctx.agent.recommendationEngine.run({ + const recResponse = await c.agent.recommendationEngine.run({ analysis, context: input.query }); @@ -825,7 +825,7 @@ const agent = createAgent({ recommendations ? `, generated ${recommendations.length} recommendations` : '' }`; - ctx.logger.info('Workflow completed successfully'); + c.logger.info('Workflow completed successfully'); return { searchResults, @@ -874,22 +874,22 @@ const agent = createAgent({ estimatedDuration: z.string().optional() }) }, - handler: async (ctx, input) => { + handler: async (c, 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', { + const stream = await c.stream.create('llm-response', { contentType: 'text/plain', metadata: { - userId: userId || ctx.sessionId, + userId: userId || c.sessionId, model: 'gpt-5-nano', type: 'llm-generation' } }); // Background task for streaming LLM response - ctx.waitUntil(async () => { + c.waitUntil(async () => { const { textStream } = streamText({ model: openai('gpt-5-nano'), prompt: prompt || 'Tell me a short story' @@ -900,7 +900,7 @@ const agent = createAgent({ // Background task for analytics if (includeAnalytics && userId) { - ctx.waitUntil(async () => { + c.waitUntil(async () => { await logRequestAnalytics(userId, { action: 'stream_created', streamId: stream.id, @@ -911,7 +911,7 @@ const agent = createAgent({ // Background task for user activity tracking if (userId) { - ctx.waitUntil(async () => { + c.waitUntil(async () => { await updateUserActivity(userId, 'llm_stream_request'); }); } @@ -925,15 +925,15 @@ const agent = createAgent({ } if (action === 'multi-step-stream') { - const progressStream = await ctx.stream.create('progress', { + const progressStream = await c.stream.create('progress', { contentType: 'application/json', metadata: { type: 'multi-step-progress', - userId: userId || ctx.sessionId + userId: userId || c.sessionId } }); - ctx.waitUntil(async () => { + c.waitUntil(async () => { try { const steps = [ { name: 'Analyzing input', duration: 1000 }, @@ -1195,7 +1195,7 @@ const agent = createAgent({ confidence: z.number() }) }, - handler: async (ctx, input) => { + handler: async (c, input) => { // Process query and generate result const result = await processQuery(input.query); @@ -1212,7 +1212,7 @@ agent.createEval({ name: 'confidence-check', description: 'Ensures confidence score meets minimum threshold' }, - handler: async (ctx, input, output) => { + handler: async (c, input, output) => { const passed = output.confidence >= 0.8; return { @@ -1254,15 +1254,15 @@ import { createAgent } from '@agentuity/runtime'; import { SpanStatusCode } from '@opentelemetry/api'; const agent = createAgent({ - handler: async (ctx, input) => { - return ctx.tracer.startActiveSpan('process-request', async (span) => { + handler: async (c, input) => { + return c.tracer.startActiveSpan('process-request', async (span) => { try { // Add attributes to the span span.setAttribute('data.type', typeof input); - span.setAttribute('agent.name', ctx.agentName || 'unknown'); + span.setAttribute('agent.name', c.agentName || 'unknown'); // Create a child span for data processing - return await ctx.tracer.startActiveSpan('process-data', async (childSpan) => { + return await c.tracer.startActiveSpan('process-data', async (childSpan) => { try { // Add event to the span childSpan.addEvent('processing-started', { @@ -1303,7 +1303,7 @@ const agent = createAgent({ message: (error as Error).message }); - ctx.logger.error('Error processing request', error); + c.logger.error('Error processing request', error); throw error; } finally { span.end(); @@ -1351,7 +1351,7 @@ const agent = createAgent({ }), stream: true }, - handler: async (ctx, input) => { + handler: async (c, input) => { const { textStream } = streamText({ model: openai('gpt-5-nano'), prompt: input.prompt @@ -1389,9 +1389,9 @@ const agent = createAgent({ }), stream: true }, - handler: async (ctx, input) => { + handler: async (c, input) => { // Call the expert agent and stream its response - const response = await ctx.agent.historyExpert.run({ + const response = await c.agent.historyExpert.run({ question: input.question }); diff --git a/content/v1/Guides/agent-communication.mdx b/content/v1/Guides/agent-communication.mdx index c1c75033..03e503c6 100644 --- a/content/v1/Guides/agent-communication.mdx +++ b/content/v1/Guides/agent-communication.mdx @@ -36,9 +36,9 @@ const coordinatorAgent = createAgent({ input: z.object({ text: z.string() }), output: z.object({ result: z.string() }) }, - handler: async (ctx, input) => { + handler: async (c, input) => { // Call another agent - const enriched = await ctx.agent.enrichmentAgent.run({ + const enriched = await c.agent.enrichmentAgent.run({ text: input.text }); @@ -62,19 +62,19 @@ Sequential execution processes data through a series of agents, where each agent ```typescript const pipelineAgent = createAgent({ - handler: async (ctx, input) => { + handler: async (c, input) => { // Step 1: Validate input - const validated = await ctx.agent.validatorAgent.run({ + const validated = await c.agent.validatorAgent.run({ data: input.rawData }); // Step 2: Enrich with additional data - const enriched = await ctx.agent.enrichmentAgent.run({ + const enriched = await c.agent.enrichmentAgent.run({ data: validated.cleanData }); // Step 3: Analyze the enriched data - const analyzed = await ctx.agent.analysisAgent.run({ + const analyzed = await c.agent.analysisAgent.run({ data: enriched.enrichedData }); @@ -97,12 +97,12 @@ Parallel execution runs multiple agents simultaneously when their operations are ```typescript const searchAgent = createAgent({ - handler: async (ctx, input) => { + handler: async (c, input) => { // Execute all searches in parallel const [webResults, dbResults, vectorResults] = await Promise.all([ - ctx.agent.webSearchAgent.run({ query: input.query }), - ctx.agent.databaseAgent.run({ query: input.query }), - ctx.agent.vectorSearchAgent.run({ query: input.query }) + c.agent.webSearchAgent.run({ query: input.query }), + c.agent.databaseAgent.run({ query: input.query }), + c.agent.vectorSearchAgent.run({ query: input.query }) ]); // Merge and rank results @@ -141,7 +141,7 @@ const routerAgent = createAgent({ schema: { input: z.object({ userMessage: z.string() }) }, - handler: async (ctx, input) => { + handler: async (c, input) => { // Classify intent with Groq (fast inference via AI Gateway) const intent = await generateObject({ model: groq('llama-3.3-70b'), // Groq (fast inference via AI Gateway) for quick classification @@ -151,7 +151,7 @@ const routerAgent = createAgent({ temperature: 0.0 // Deterministic output for routing decisions }); - ctx.logger.info('Intent classified', { + c.logger.info('Intent classified', { type: intent.object.agentType, confidence: intent.object.confidence }); @@ -159,25 +159,25 @@ const routerAgent = createAgent({ // Route based on classified intent switch (intent.object.agentType) { case 'support': - return await ctx.agent.supportAgent.run({ + return await c.agent.supportAgent.run({ message: input.userMessage, context: intent.object.reasoning }); case 'sales': - return await ctx.agent.salesAgent.run({ + return await c.agent.salesAgent.run({ message: input.userMessage, context: intent.object.reasoning }); case 'technical': - return await ctx.agent.technicalAgent.run({ + return await c.agent.technicalAgent.run({ message: input.userMessage, context: intent.object.reasoning }); case 'billing': - return await ctx.agent.billingAgent.run({ + return await c.agent.billingAgent.run({ message: input.userMessage, context: intent.object.reasoning }); @@ -200,9 +200,9 @@ Build complex pipelines with checkpoints and conditional branching: ```typescript const contentModerationAgent = createAgent({ - handler: async (ctx, input) => { + handler: async (c, input) => { // Stage 1: Initial screening - const screening = await ctx.agent.screeningAgent.run({ + const screening = await c.agent.screeningAgent.run({ content: input.text }); @@ -216,13 +216,13 @@ const contentModerationAgent = createAgent({ // Stage 2: Detailed analysis (parallel) const [sentiment, toxicity, pii] = await Promise.all([ - ctx.agent.sentimentAgent.run({ text: input.text }), - ctx.agent.toxicityAgent.run({ text: input.text }), - ctx.agent.piiDetectionAgent.run({ text: input.text }) + c.agent.sentimentAgent.run({ text: input.text }), + c.agent.toxicityAgent.run({ text: input.text }), + c.agent.piiDetectionAgent.run({ text: input.text }) ]); // Stage 3: Final decision - const decision = await ctx.agent.decisionAgent.run({ + const decision = await c.agent.decisionAgent.run({ screening, sentiment, toxicity, @@ -248,16 +248,16 @@ const documentAnalysisAgent = createAgent({ sections: z.array(z.string()) }) }, - handler: async (ctx, input) => { + handler: async (c, input) => { // Fan-out: Analyze each section in parallel const sectionAnalyses = await Promise.all( input.sections.map(section => - ctx.agent.sectionAnalyzer.run({ text: section }) + c.agent.sectionAnalyzer.run({ text: section }) ) ); // Fan-in: Aggregate results - const summary = await ctx.agent.summaryAgent.run({ + const summary = await c.agent.summaryAgent.run({ analyses: sectionAnalyses }); @@ -274,7 +274,7 @@ const documentAnalysisAgent = createAgent({ ```typescript const results = await Promise.allSettled( input.items.map(item => - ctx.agent.processingAgent.run({ item }) + c.agent.processingAgent.run({ item }) ) ); @@ -287,7 +287,7 @@ const failed = results .map(r => r.reason); if (failed.length > 0) { - ctx.logger.warn('Some operations failed', { failed }); + c.logger.warn('Some operations failed', { failed }); } return { successful, failed: failed.length }; @@ -301,12 +301,12 @@ By default, errors propagate through the call chain, stopping execution immediat ```typescript const agent = createAgent({ - handler: async (ctx, input) => { + handler: async (c, input) => { // If validatorAgent throws, execution stops here - const validated = await ctx.agent.validatorAgent.run(input); + const validated = await c.agent.validatorAgent.run(input); // This line never executes if validation fails - const processed = await ctx.agent.processorAgent.run(validated); + const processed = await c.agent.processorAgent.run(validated); return processed; } @@ -321,23 +321,23 @@ For optional operations, catch errors and continue with reduced functionality: ```typescript const agent = createAgent({ - handler: async (ctx, input) => { + handler: async (c, input) => { let enrichedData = input.data; // Try to enrich, but continue if it fails try { - const enrichment = await ctx.agent.enrichmentAgent.run({ + const enrichment = await c.agent.enrichmentAgent.run({ data: input.data }); enrichedData = enrichment.data; } catch (error) { - ctx.logger.warn('Enrichment failed, using original data', { + c.logger.warn('Enrichment failed, using original data', { error: error instanceof Error ? error.message : String(error) }); } // Process with enriched data (or original if enrichment failed) - return await ctx.agent.processorAgent.run({ + return await c.agent.processorAgent.run({ data: enrichedData }); } @@ -377,10 +377,10 @@ async function callWithRetry( } const agent = createAgent({ - handler: async (ctx, input) => { + handler: async (c, input) => { // Retry unreliable external agent call const result = await callWithRetry(() => - ctx.agent.externalServiceAgent.run(input) + c.agent.externalServiceAgent.run(input) ); return result; @@ -394,15 +394,15 @@ Provide alternative paths when primary agents fail: ```typescript const agent = createAgent({ - handler: async (ctx, input) => { + handler: async (c, input) => { try { // Try primary agent - return await ctx.agent.primaryAgent.run(input); + return await c.agent.primaryAgent.run(input); } catch (error) { - ctx.logger.warn('Primary agent failed, using fallback', { error }); + c.logger.warn('Primary agent failed, using fallback', { error }); // Fall back to alternative agent - return await ctx.agent.fallbackAgent.run(input); + return await c.agent.fallbackAgent.run(input); } } }); @@ -416,14 +416,14 @@ Transform agent outputs to match the expected input of downstream agents: ```typescript const agent = createAgent({ - handler: async (ctx, input) => { + handler: async (c, input) => { // Agent 1 returns { analysisResult: {...} } - const analysis = await ctx.agent.analysisAgent.run({ + const analysis = await c.agent.analysisAgent.run({ text: input.text }); // Agent 2 expects { data: {...}, metadata: {...} } - const processed = await ctx.agent.processingAgent.run({ + const processed = await c.agent.processingAgent.run({ data: analysis.analysisResult, metadata: { timestamp: new Date().toISOString(), @@ -468,24 +468,24 @@ Build pipelines that progressively transform data: ```typescript const transformationAgent = createAgent({ - handler: async (ctx, input) => { + handler: async (c, input) => { // Raw text → Structured data - const structured = await ctx.agent.parserAgent.run({ + const structured = await c.agent.parserAgent.run({ text: input.rawText }); // Structured data → Validated data - const validated = await ctx.agent.validatorAgent.run({ + const validated = await c.agent.validatorAgent.run({ data: structured.parsed }); // Validated data → Enriched data - const enriched = await ctx.agent.enrichmentAgent.run({ + const enriched = await c.agent.enrichmentAgent.run({ data: validated.clean }); // Enriched data → Final output - const final = await ctx.agent.formatterAgent.run({ + const final = await c.agent.formatterAgent.run({ data: enriched.enriched }); @@ -508,9 +508,9 @@ const researchAgent = createAgent({ sources: z.array(z.string()) }) }, - handler: async (ctx, input) => { + handler: async (c, input) => { // Step 1: Search for relevant information - const searchResults = await ctx.agent.searchAgent.run({ + const searchResults = await c.agent.searchAgent.run({ query: input.query, limit: 10 }); @@ -518,12 +518,12 @@ const researchAgent = createAgent({ // Step 2: Summarize findings (parallel) const summaries = await Promise.all( searchResults.results.map(result => - ctx.agent.summaryAgent.run({ text: result.content }) + c.agent.summaryAgent.run({ text: result.content }) ) ); // Step 3: Analyze and extract insights - const analysis = await ctx.agent.analysisAgent.run({ + const analysis = await c.agent.analysisAgent.run({ summaries: summaries.map(s => s.summary), originalQuery: input.query }); @@ -547,9 +547,9 @@ const approvalAgent = createAgent({ requester: z.string() }) }, - handler: async (ctx, input) => { + handler: async (c, input) => { // Step 1: Validate request format - const validation = await ctx.agent.validatorAgent.run({ + const validation = await c.agent.validatorAgent.run({ request: input.request }); @@ -562,7 +562,7 @@ const approvalAgent = createAgent({ } // Step 2: Check user permissions - const permissions = await ctx.agent.permissionsAgent.run({ + const permissions = await c.agent.permissionsAgent.run({ userId: input.requester, action: input.request.action }); @@ -576,13 +576,13 @@ const approvalAgent = createAgent({ // Step 3: Risk assessment (parallel checks) const [financialRisk, securityRisk, complianceCheck] = await Promise.all([ - ctx.agent.financialRiskAgent.run(input.request), - ctx.agent.securityRiskAgent.run(input.request), - ctx.agent.complianceAgent.run(input.request) + c.agent.financialRiskAgent.run(input.request), + c.agent.securityRiskAgent.run(input.request), + c.agent.complianceAgent.run(input.request) ]); // Step 4: Final approval decision - const decision = await ctx.agent.decisionAgent.run({ + const decision = await c.agent.decisionAgent.run({ request: input.request, permissions, financialRisk, @@ -599,7 +599,7 @@ const approvalAgent = createAgent({ ```typescript const dataEnrichmentAgent = createAgent({ - handler: async (ctx, input) => { + handler: async (c, input) => { // Start with raw user data let userData = input.userData; @@ -609,9 +609,9 @@ const dataEnrichmentAgent = createAgent({ behavioralData, preferenceData ] = await Promise.all([ - ctx.agent.demographicAgent.run({ userId: userData.id }), - ctx.agent.behavioralAgent.run({ userId: userData.id }), - ctx.agent.preferenceAgent.run({ userId: userData.id }) + c.agent.demographicAgent.run({ userId: userData.id }), + c.agent.behavioralAgent.run({ userId: userData.id }), + c.agent.preferenceAgent.run({ userId: userData.id }) ]); // Merge all data @@ -623,19 +623,19 @@ const dataEnrichmentAgent = createAgent({ }; // Generate insights from enriched data - const insights = await ctx.agent.insightsAgent.run({ + const insights = await c.agent.insightsAgent.run({ enrichedData: merged }); // Store for future use try { - await ctx.kv.set('enriched-users', userData.id, { + await c.kv.set('enriched-users', userData.id, { data: merged, insights: insights, updatedAt: new Date().toISOString() }, { ttl: 86400 }); // 24 hours } catch (error) { - ctx.logger.warn('Failed to cache enriched data', { error }); + c.logger.warn('Failed to cache enriched data', { error }); } return { @@ -654,7 +654,7 @@ For parent-child agent hierarchies, the SDK provides specialized patterns. Subag ```typescript // Call a subagent from anywhere -const result = await ctx.agent.team.members.run({ +const result = await c.agent.team.members.run({ action: 'list' }); @@ -682,7 +682,7 @@ const analysisAgent = createAgent({ /* analyzes data */ }); // Bad - monolithic agent doing everything const megaAgent = createAgent({ - handler: async (ctx, input) => { + handler: async (c, input) => { // Validates, enriches, analyzes all in one place } }); @@ -702,7 +702,7 @@ const sourceAgent = createAgent({ metadata: z.object({ timestamp: z.string() }) }) }, - handler: async (ctx, input) => { + handler: async (c, input) => { return { data: 'result', metadata: { timestamp: new Date().toISOString() } @@ -717,9 +717,9 @@ const consumerAgent = createAgent({ metadata: z.object({ timestamp: z.string() }) }) }, - handler: async (ctx, input) => { + handler: async (c, input) => { // TypeScript knows the exact shape of input - const result = await ctx.agent.sourceAgent.run({}); + const result = await c.agent.sourceAgent.run({}); // This is type-safe - TypeScript validates compatibility return await processData(result); @@ -734,15 +734,15 @@ Choose the right error handling strategy for each operation: **Fail-fast** for critical operations: ```typescript // No try-catch - let errors propagate -const validated = await ctx.agent.validatorAgent.run(input); +const validated = await c.agent.validatorAgent.run(input); ``` **Graceful degradation** for optional operations: ```typescript try { - await ctx.agent.optionalAgent.run(input); + await c.agent.optionalAgent.run(input); } catch (error) { - ctx.logger.warn('Optional operation failed', { error }); + c.logger.warn('Optional operation failed', { error }); } ``` @@ -752,18 +752,18 @@ Use logging and tracing to monitor multi-agent workflows: ```typescript const agent = createAgent({ - handler: async (ctx, input) => { + handler: async (c, input) => { const startTime = Date.now(); - ctx.logger.info('Starting multi-agent workflow', { - sessionId: ctx.sessionId + c.logger.info('Starting multi-agent workflow', { + sessionId: c.sessionId }); - const result = await ctx.agent.processingAgent.run(input); + const result = await c.agent.processingAgent.run(input); - ctx.logger.info('Workflow completed', { + c.logger.info('Workflow completed', { duration: Date.now() - startTime, - sessionId: ctx.sessionId + sessionId: c.sessionId }); return result; @@ -777,16 +777,16 @@ Agent calls execute within the same session context, sharing state: ```typescript const coordinatorAgent = createAgent({ - handler: async (ctx, input) => { + handler: async (c, input) => { // Store data in thread state ctx.thread.state.set('startTime', Date.now()); ctx.thread.state.set('userId', input.userId); // Called agents can access the same thread state - const result = await ctx.agent.processingAgent.run(input); + const result = await c.agent.processingAgent.run(input); // All agents share the same sessionId - ctx.logger.info('Session ID:', ctx.sessionId); + c.logger.info('Session ID:', c.sessionId); return result; } @@ -805,7 +805,7 @@ When using LLM-based routing, handle classification failures gracefully: ```typescript const routerAgent = createAgent({ - handler: async (ctx, input) => { + handler: async (c, input) => { try { const intent = await generateObject({ model: groq('llama-3.3-70b'), @@ -817,30 +817,30 @@ const routerAgent = createAgent({ // Route based on intent switch (intent.object.agentType) { case 'support': - return await ctx.agent.supportAgent.run({ + return await c.agent.supportAgent.run({ message: input.userMessage, context: intent.object.reasoning }); case 'sales': - return await ctx.agent.salesAgent.run({ + return await c.agent.salesAgent.run({ message: input.userMessage, context: intent.object.reasoning }); case 'technical': - return await ctx.agent.technicalAgent.run({ + return await c.agent.technicalAgent.run({ message: input.userMessage, context: intent.object.reasoning }); } } catch (error) { - ctx.logger.error('Intent classification failed', { + c.logger.error('Intent classification failed', { error: error instanceof Error ? error.message : String(error) }); // Fallback to default agent - return await ctx.agent.defaultAgent.run({ + return await c.agent.defaultAgent.run({ message: input.userMessage }); } @@ -852,8 +852,8 @@ This ensures your system remains functional even when classification fails. ## Next Steps -- [Subagents](/Guides/subagents) - Parent-child agent hierarchies and coordination patterns -- [Schema Validation](/Guides/schema-validation) - Type-safe schemas for agent inputs and outputs -- [Events](/Guides/events) - Monitor agent lifecycle and execution -- [Error Handling](/SDK/error-handling) - Comprehensive error handling strategies -- [Core Concepts](/SDK/core-concepts) - Detailed agent communication API reference +- [Subagents](/Guides/subagents): Parent-child agent hierarchies and coordination patterns +- [Schema Validation](/Guides/schema-validation): Type-safe schemas for agent inputs and outputs +- [Events](/Guides/events): Monitor agent lifecycle and execution +- [Error Handling](/SDK/error-handling): Comprehensive error handling strategies +- [Core Concepts](/SDK/core-concepts): Detailed agent communication API reference diff --git a/content/v1/Guides/agent-logging.mdx b/content/v1/Guides/agent-logging.mdx new file mode 100644 index 00000000..20773f46 --- /dev/null +++ b/content/v1/Guides/agent-logging.mdx @@ -0,0 +1,416 @@ +--- +title: Agent Logging +description: Structured, real-time insights into agent execution for debugging and monitoring +--- + +# Agent Logging + +Agent logging provides structured, real-time insights into your agent's execution. Effective logging helps you debug issues, monitor behavior, and understand agent decision-making. + +## Logging Interface + +The Agentuity platform provides persistent, searchable logs with real-time streaming for all deployed agents. + +TODO: Update screenshot for v1 Logs Overview + +### Log Overview + +The Logs dashboard displays: + +- **Timestamps**: Precise timing for each log entry +- **Severity levels**: TRACE, DEBUG, INFO, WARN, ERROR, FATAL for categorization +- **Source identification**: Which component generated the log +- **Detailed messages**: Context about agent actions + +### Search and Filtering + +**AI-Powered Search:** +Use natural language queries to find log entries. Click the purple sparkle icon and enter your search: + +TODO: Update screenshot for v1 AI-Powered Log Search + +**Filtering Options:** +- **Severity**: Filter by log level (TRACE, DEBUG, INFO, WARN, ERROR, FATAL) +- **Project**: Scope logs to specific projects +- **Agent**: View logs from specific agents +- **Session ID**: Filter logs for a particular session +- **Deployment ID**: View logs from specific deployments +- **Time Range**: Focus on specific time periods + +### Detailed Log Analysis + +Each log entry provides comprehensive context and can be expanded for full details: + +TODO: Update screenshot for v1 Detailed Log Entry View + +## Logging Best Practices + +### 1. Use Appropriate Log Levels + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const agent = createAgent({ + handler: async (c, input) => { + // TRACE: Very detailed step-by-step execution (hidden in production) + c.logger.trace('RAG retrieval step 1: Embedding query'); + c.logger.trace('RAG retrieval step 2: Searching vector store', { query: input.question }); + c.logger.trace('RAG retrieval step 3: Ranking results', { count: results.length }); + + // DEBUG: Detailed information for debugging + c.logger.debug('Cache lookup', { key: cacheKey, hit: true }); + + // INFO: Normal flow and state changes + c.logger.info('Processing user request', { userId: input.userId, action: 'search' }); + + // WARN: Potential issues that don't stop execution + c.logger.warn('Rate limit approaching', { remaining: 5, limit: 100 }); + + // ERROR: Failures requiring attention but execution continues + c.logger.error('Failed to fetch data', { error: error.message, retrying: true }); + + // FATAL: Critical errors that terminate execution + // c.logger.fatal('Application cannot continue: Critical initialization failure'); + + return { result: 'success' }; + } +}); +``` + +**When to use TRACE:** +- Multi-step algorithms showing each calculation +- RAG systems showing retrieval, ranking, generation steps +- Complex data transformations showing each stage +- Multi-agent workflows showing each agent call + +**When to use DEBUG:** +- Cache hits/misses +- Variable values during execution +- Conditional branch decisions +- Database query details + +**When to use INFO:** +- Request processing started/completed +- State changes +- Successful operations +- Normal workflow milestones + +**When to use WARN:** +- Approaching resource limits +- Deprecated feature usage +- Fallback behavior triggered +- Recoverable errors + +**When to use ERROR:** +- Failed operations with retry +- External service failures +- Validation errors +- Caught exceptions + +**When to use FATAL:** +- Critical errors that require immediate termination +- Unrecoverable initialization failures +- System-level failures that prevent operation +- Note: FATAL terminates the process immediately + +### 2. Use Structured Data + +Use structured data to provide context and make logs easier to parse and analyze: + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const agent = createAgent({ + handler: async (c, input) => { + // Good: Use objects for structured context + c.logger.info('API call completed', { + endpoint: '/api/users', + method: 'GET', + duration: 234, + status: 200, + contentType: 'application/json' + }); + + // Extract variables for dynamic values + const orderId = input.order.id; + const itemCount = input.order.items.length; + + c.logger.info('Order processed', { + orderId, + itemCount, + total: input.order.total + }); + + return { success: true }; + } +}); +``` + +### 3. Include Relevant Context + +Log enough information to understand what happened without re-running: + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const agent = createAgent({ + handler: async (c, input) => { + // Good - includes context + c.logger.info('Order processed', { + orderId: input.order.id, + customerId: input.customer.id, + total: input.order.total, + itemCount: input.order.items.length + }); + + // Less helpful - missing context + // c.logger.info('Order done'); + + return { success: true }; + } +}); +``` + +### 4. Include Agentuity-Specific Information + +Add Agentuity-specific information to help with debugging and monitoring: + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const agent = createAgent({ + handler: async (c, input) => { + // Include available Agentuity context + c.logger.info('Agent processing request', { + agentName: c.agentName, + sessionId: c.sessionId + // Note: c.agent.id and req.trigger not available in v1 + }); + + // For errors, include additional context + try { + const result = await processData(input); + return result; + } catch (error) { + c.logger.error('Agent execution failed', { + agentName: c.agentName, + sessionId: c.sessionId, + error: error.message + }); + throw error; + } + } +}); +``` + +**Note:** v1 does not provide `c.agent.id` or `req.trigger` properties. Use `c.agentName` for agent identification and `c.sessionId` for request tracking. + +### 5. Log Decision Points + +Help future debugging by logging key decisions: + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const agent = createAgent({ + handler: async (c, input) => { + const cacheKey = `user:${input.userId}:profile`; + const cached = await c.kv.get('cache', cacheKey); + + if (cached.exists) { + const cacheAge = Date.now() - cached.data.timestamp; + c.logger.info('Using cached response', { + cacheKey, + age: cacheAge + }); + return cached.data.value; + } else { + c.logger.info('Cache miss, fetching fresh data', { + cacheKey, + reason: 'not found' + }); + const freshData = await fetchUserProfile(input.userId); + await c.kv.set('cache', cacheKey, { value: freshData, timestamp: Date.now() }); + return freshData; + } + } +}); +``` + +### 6. Use Child Loggers for Different Tasks + +Create child loggers to organize logging for different parts of your workflow: + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const agent = createAgent({ + handler: async (c, input) => { + // Create child loggers for different tasks + const orderLogger = c.logger.child({ component: 'order-processing' }); + const paymentLogger = c.logger.child({ component: 'payment' }); + const inventoryLogger = c.logger.child({ component: 'inventory' }); + + // Use specific loggers for their respective operations + orderLogger.info('Starting order validation'); + + const orderId = input.order.id; + const itemCount = input.order.items.length; + const paymentAmount = input.order.total; + + orderLogger.info('Order validated', { + orderId, + itemCount, + total: paymentAmount + }); + + paymentLogger.info('Processing payment', { + orderId, + amount: paymentAmount + }); + + inventoryLogger.info('Checking inventory', { + orderId, + items: itemCount + }); + + return { success: true, orderId }; + } +}); +``` + +**Benefits of child loggers:** +- Automatic context propagation (component name) +- Easier filtering and searching in logs +- Clear separation of concerns +- Consistent formatting across related operations + +## Common Logging Patterns + +### Pattern 1: RAG System with Detailed Tracing + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const ragAgent = createAgent({ + schema: { + input: z.object({ question: z.string() }), + output: z.object({ answer: z.string(), sources: z.array(z.string()) }) + }, + handler: async (c, input) => { + c.logger.trace('RAG pipeline started'); + + // Step 1: Embedding + c.logger.trace('Generating query embedding'); + const embedding = await generateEmbedding(input.question); + c.logger.debug('Query embedding generated', { dimensions: embedding.length }); + + // Step 2: Retrieval + c.logger.trace('Searching vector store'); + const results = await c.vector.search('knowledge-base', { + query: input.question, + limit: 5, + similarity: 0.7 + }); + c.logger.info('Retrieved documents', { count: results.length }); + + // Step 3: Generation + c.logger.trace('Generating answer from context'); + const answer = await generateAnswer(input.question, results); + c.logger.info('Answer generated', { length: answer.length }); + + return { + answer, + sources: results.map(r => r.key) + }; + } +}); +``` + +### Pattern 2: Multi-Agent Workflow + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const coordinatorAgent = createAgent({ + handler: async (c, input) => { + const workflowLogger = c.logger.child({ workflow: 'data-processing' }); + + workflowLogger.info('Workflow started', { inputSize: input.data.length }); + + // Step 1: Validation + workflowLogger.debug('Calling validation agent'); + const validated = await c.agent.validator.run(input); + workflowLogger.info('Validation complete', { valid: validated.isValid }); + + // Step 2: Processing + workflowLogger.debug('Calling processing agent'); + const processed = await c.agent.processor.run(validated); + workflowLogger.info('Processing complete', { recordsProcessed: processed.count }); + + // Step 3: Storage + workflowLogger.debug('Calling storage agent'); + const stored = await c.agent.storage.run(processed); + workflowLogger.info('Storage complete', { stored: stored.success }); + + workflowLogger.info('Workflow completed successfully'); + + return { + success: true, + processedCount: processed.count + }; + } +}); +``` + +### Pattern 3: Error Handling with Context + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const agent = createAgent({ + handler: async (c, input) => { + const startTime = Date.now(); + + try { + c.logger.info('Processing request', { + agentName: c.agentName, + sessionId: c.sessionId, + userId: input.userId + }); + + const result = await performComplexOperation(input); + + const duration = Date.now() - startTime; + c.logger.info('Request completed successfully', { + duration, + resultSize: JSON.stringify(result).length + }); + + return result; + + } catch (error) { + const duration = Date.now() - startTime; + + c.logger.error('Request failed', { + agentName: c.agentName, + sessionId: c.sessionId, + userId: input.userId, + duration, + error: error.message, + stack: error.stack + }); + + // Re-throw or handle as appropriate + throw error; + } + } +}); +``` + +## Next Steps + +- [Agent Communication](/Guides/agent-communication): Share state between agents +- [Sessions and Threads](/Guides/sessions-threads): Manage conversation state +- [API Reference](/SDK/api-reference): Complete logging API documentation diff --git a/content/v1/Guides/evaluations.mdx b/content/v1/Guides/evaluations.mdx index b61ff6e5..713ff026 100644 --- a/content/v1/Guides/evaluations.mdx +++ b/content/v1/Guides/evaluations.mdx @@ -38,7 +38,7 @@ const agent = createAgent({ input: z.object({ question: z.string() }), output: z.object({ answer: z.string(), confidence: z.number() }), }, - handler: async (ctx, input) => { + handler: async (c, input) => { const answer = await generateAnswer(input.question); return { answer, confidence: 0.95 }; }, @@ -50,7 +50,7 @@ agent.createEval({ name: 'confidence-check', description: 'Ensures confidence score meets minimum threshold' }, - handler: async (ctx, input, output) => { + handler: async (c, input, output) => { const passed = output.confidence >= 0.8; return { @@ -101,14 +101,14 @@ const agent = createAgent({ input: z.object({ query: z.string() }), output: z.object({ result: z.string() }), }, - handler: async (ctx, input) => { + handler: async (c, input) => { return { result: `Processed: ${input.query}` }; }, }); agent.createEval({ metadata: { name: 'query-in-result' }, - handler: async (ctx, input, output) => { + handler: async (c, input, output) => { // Handler receives both input and output const queryInResult = output.result.includes(input.query); @@ -130,14 +130,14 @@ const agent = createAgent({ schema: { input: z.object({ message: z.string() }), }, - handler: async (ctx, input) => { + handler: async (c, input) => { return `Received: ${input.message}`; }, }); agent.createEval({ metadata: { name: 'input-validation' }, - handler: async (ctx, input) => { + handler: async (c, input) => { // Handler receives only input const isValid = input.message.length > 0 && input.message.length <= 500; @@ -173,7 +173,7 @@ Use for clear yes/no validation (compliance checks, format verification). ```typescript agent.createEval({ metadata: { name: 'length-check' }, - handler: async (ctx, input, output) => { + handler: async (c, input, output) => { const passed = output.answer.length >= 50; return { success: true, @@ -202,7 +202,7 @@ Use for quality measurement and A/B testing. ```typescript agent.createEval({ metadata: { name: 'quality-score' }, - handler: async (ctx, input, output) => { + handler: async (c, input, output) => { let score = 0; if (output.answer.length >= 100) score += 0.4; if (output.answer.includes(input.question)) score += 0.3; @@ -231,7 +231,7 @@ Use when the eval itself fails to execute. ```typescript agent.createEval({ metadata: { name: 'external-validation' }, - handler: async (ctx, input, output) => { + handler: async (c, input, output) => { try { const response = await fetch('https://api.example.com/validate', { method: 'POST', @@ -256,37 +256,37 @@ agent.createEval({ Eval handlers receive the same context (`EvalContext`) as agent handlers: **Available Properties:** -- **Identifiers**: `ctx.sessionId`, `ctx.agentName` -- **Storage**: `ctx.kv`, `ctx.vector`, `ctx.objectstore`, `ctx.stream` -- **Observability**: `ctx.logger`, `ctx.tracer` -- **State**: `ctx.session`, `ctx.thread`, `ctx.state` -- **Agents**: `ctx.agent`, `ctx.current`, `ctx.parent` -- **Utilities**: `ctx.waitUntil()` +- **Identifiers**: `c.sessionId`, `c.agentName` +- **Storage**: `c.kv`, `c.vector`, `c.objectstore`, `c.stream` +- **Observability**: `c.logger`, `c.tracer` +- **State**: `c.session`, `c.thread`, `c.state` +- **Agents**: `c.agent`, `c.current`, `c.parent` +- **Utilities**: `c.waitUntil()` **Example Using Multiple Context Features:** ```typescript agent.createEval({ metadata: { name: 'comprehensive-tracker' }, - handler: async (ctx, input, output) => { - const startTime = ctx.state.get('startTime') as number; + handler: async (c, input, output) => { + const startTime = c.state.get('startTime') as number; const duration = Date.now() - startTime; // Store metrics - await ctx.kv.set('performance', ctx.sessionId, { + await c.kv.set('performance', c.sessionId, { duration, timestamp: Date.now(), - agentName: ctx.agentName + agentName: c.agentName }); // Log with tracer - ctx.tracer.startActiveSpan('eval-tracking', (span) => { + c.tracer.startActiveSpan('eval-tracking', (span) => { span.setAttribute('duration_ms', duration); span.end(); }); // Log result - ctx.logger.info('Performance tracked', { sessionId: ctx.sessionId, duration }); + c.logger.info('Performance tracked', { sessionId: c.sessionId, duration }); return { success: true, @@ -310,7 +310,7 @@ const agent = createAgent({ keywords: z.array(z.string()) }), }, - handler: async (ctx, input) => { + handler: async (c, input) => { return { summary: generateSummary(input.text), keywords: extractKeywords(input.text) @@ -321,7 +321,7 @@ const agent = createAgent({ // Eval 1: Summary length agent.createEval({ metadata: { name: 'summary-length' }, - handler: async (ctx, input, output) => { + handler: async (c, input, output) => { const passed = output.summary.length >= 20 && output.summary.length <= 200; return { success: true, @@ -334,7 +334,7 @@ agent.createEval({ // Eval 2: Quality score agent.createEval({ metadata: { name: 'overall-quality' }, - handler: async (ctx, input, output) => { + handler: async (c, input, output) => { let score = 0; if (output.summary.length >= 50) score += 0.5; if (output.keywords.length >= 3) score += 0.5; @@ -385,7 +385,7 @@ const customerServiceAgent = createAgent({ actionsTaken: z.array(z.string()) }) }, - handler: async (ctx, input) => { + handler: async (c, input) => { // Agent processes customer request return { response: "Your order has been cancelled and refund processed.", @@ -399,7 +399,7 @@ customerServiceAgent.createEval({ name: 'task-completion-check', description: 'Evaluates if agent successfully completed customer request' }, - handler: async (ctx, input, output) => { + handler: async (c, input, output) => { const { object } = await generateObject({ model: openai('gpt-5-nano'), schema: z.object({ @@ -434,7 +434,7 @@ Did the agent fully address the customer's needs? Consider: ### Hallucination Detection (Reference-Based) -Verify that agent output is grounded in retrieved context, detecting unsupported claims. This pattern stores retrieved documents in `ctx.state` during handler execution for eval access: +Verify that agent output is grounded in retrieved context, detecting unsupported claims. This pattern stores retrieved documents in `c.state` during handler execution for eval access: ```typescript import { generateObject } from 'ai'; @@ -449,15 +449,15 @@ const ragAgent = createAgent({ sources: z.array(z.string()) }) }, - handler: async (ctx, input) => { + handler: async (c, input) => { // Retrieve relevant documents - const results = await ctx.vector.search('knowledge-base', { + const results = await c.vector.search('knowledge-base', { query: input.question, limit: 3 }); // Store retrieved context for eval access - ctx.state.set('retrievedDocs', results.map(r => r.metadata?.text || '')); + c.state.set('retrievedDocs', results.map(r => r.metadata?.text || '')); // Generate answer using LLM + context const answer = await generateAnswer(input.question, results); @@ -474,9 +474,9 @@ ragAgent.createEval({ name: 'hallucination-check', description: 'Detects claims not supported by retrieved sources' }, - handler: async (ctx, input, output) => { + handler: async (c, input, output) => { // Access retrieved documents from handler execution - const retrievedDocs = ctx.state.get('retrievedDocs') as string[]; + const retrievedDocs = c.state.get('retrievedDocs') as string[]; const { object } = await generateObject({ model: openai('gpt-5-nano'), @@ -509,7 +509,7 @@ Identify any claims in the answer that are NOT supported by the sources.` ``` -**State Persistence**: Data stored in `ctx.state` during agent execution persists through to eval handlers, enabling patterns like passing retrieved documents to hallucination checks. +**State Persistence**: Data stored in `c.state` during agent execution persists through to eval handlers, enabling patterns like passing retrieved documents to hallucination checks. ### Compliance Validation @@ -519,7 +519,7 @@ Check for policy violations or sensitive content: ```typescript agent.createEval({ metadata: { name: 'content-safety' }, - handler: async (ctx, input, output) => { + handler: async (c, input, output) => { const profanityList = ['badword1', 'badword2']; const piiPatterns = { email: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/, @@ -570,15 +570,15 @@ const ragAgent = createAgent({ confidence: z.number() }) }, - handler: async (ctx, input) => { - const results = await ctx.vector.search('docs', { + handler: async (c, input) => { + const results = await c.vector.search('docs', { query: input.query, limit: 5, similarity: 0.7 }); // Store for eval access - ctx.state.set('retrievedResults', results); + c.state.set('retrievedResults', results); const answer = await generateAnswer(input.query, results); @@ -592,8 +592,8 @@ ragAgent.createEval({ name: 'contextual-relevancy', description: 'Evaluates if retrieved documents are relevant to query' }, - handler: async (ctx, input, output) => { - const results = ctx.state.get('retrievedResults') as VectorSearchResult[]; + handler: async (c, input, output) => { + const results = c.state.get('retrievedResults') as VectorSearchResult[]; const { object } = await generateObject({ model: openai('gpt-5-nano'), @@ -625,7 +625,7 @@ ragAgent.createEval({ name: 'answer-relevancy', description: 'Evaluates if answer addresses the question' }, - handler: async (ctx, input, output) => { + handler: async (c, input, output) => { const { object } = await generateObject({ model: openai('gpt-5-nano'), schema: z.object({ @@ -654,8 +654,8 @@ ragAgent.createEval({ name: 'faithfulness', description: 'Checks if answer contains information not in sources' }, - handler: async (ctx, input, output) => { - const results = ctx.state.get('retrievedResults') as VectorSearchResult[]; + handler: async (c, input, output) => { + const results = c.state.get('retrievedResults') as VectorSearchResult[]; const sources = results.map(r => r.metadata?.text).join('\n\n'); const { object } = await generateObject({ @@ -694,7 +694,7 @@ Evals should handle errors gracefully to avoid breaking the eval pipeline. ```typescript agent.createEval({ metadata: { name: 'safe-external-check' }, - handler: async (ctx, input, output) => { + handler: async (c, input, output) => { try { const response = await fetch('https://api.example.com/validate', { method: 'POST', @@ -710,7 +710,7 @@ agent.createEval({ const result = await response.json(); return { success: true, passed: result.isValid }; } catch (error) { - ctx.logger.error('External validation failed', { error: error.message }); + c.logger.error('External validation failed', { error: error.message }); return { success: false, error: error.message }; } }, @@ -722,13 +722,13 @@ agent.createEval({ ```typescript agent.createEval({ metadata: { name: 'resilient-eval' }, - handler: async (ctx, input, output) => { + handler: async (c, input, output) => { // Try primary method try { const result = await validateWithLLM(output.answer); return { success: true, score: result.score, metadata: { method: 'llm' } }; } catch (error) { - ctx.logger.warn('LLM validation failed, using fallback'); + c.logger.warn('LLM validation failed, using fallback'); } // Fallback to rule-based @@ -766,7 +766,7 @@ agent.createEval({ **Key Points:** - Evals run after the response is sent to the caller -- Uses `ctx.waitUntil()` to avoid blocking +- Uses `c.waitUntil()` to avoid blocking - Each eval receives validated input/output - Results are logged and tracked automatically @@ -791,7 +791,7 @@ agent.createEval({ // Good: Single-purpose agent.createEval({ metadata: { name: 'length-check' }, - handler: async (ctx, input, output) => { + handler: async (c, input, output) => { return { success: true, passed: output.answer.length >= 50 }; }, }); @@ -799,7 +799,7 @@ agent.createEval({ // Avoid: Multiple concerns in one eval agent.createEval({ metadata: { name: 'everything-check' }, - handler: async (ctx, input, output) => { + handler: async (c, input, output) => { const lengthOk = output.answer.length >= 50; const hasKeywords = checkKeywords(output); const sentiment = analyzeSentiment(output); @@ -860,12 +860,12 @@ Track execution time and resource usage: ```typescript agent.createEval({ metadata: { name: 'performance-monitor' }, - handler: async (ctx, input, output) => { - const startTime = ctx.state.get('startTime') as number; + handler: async (c, input, output) => { + const startTime = c.state.get('startTime') as number; const duration = startTime ? Date.now() - startTime : 0; // Store metrics - await ctx.kv.set('metrics', `perf-${ctx.sessionId}`, { + await c.kv.set('metrics', `perf-${c.sessionId}`, { duration, timestamp: Date.now(), inputSize: JSON.stringify(input).length, @@ -888,8 +888,8 @@ Compare different approaches: ```typescript agent.createEval({ metadata: { name: 'ab-test-tracker' }, - handler: async (ctx, input, output) => { - const variant = ctx.state.get('variant') as 'A' | 'B'; + handler: async (c, input, output) => { + const variant = c.state.get('variant') as 'A' | 'B'; if (!variant) { return { success: false, error: 'No variant specified' }; } @@ -899,7 +899,7 @@ agent.createEval({ if (output.confidence >= 0.8) score += 0.5; // Store for analysis - await ctx.kv.set('ab-test', `${variant}-${ctx.sessionId}`, { + await c.kv.set('ab-test', `${variant}-${c.sessionId}`, { variant, score, timestamp: Date.now() @@ -912,7 +912,7 @@ agent.createEval({ ## Additional Resources -- [API Reference](/SDKs/javascript/api-reference#evaluations) - Detailed type signatures and interfaces -- [Events Guide](/SDKs/javascript/events) - Monitor eval execution with event listeners -- [Schema Validation](/SDKs/javascript/schema-validation) - Learn more about input/output validation -- [Core Concepts](/SDKs/javascript/core-concepts) - Understanding agent architecture +- [API Reference](/SDKs/javascript/api-reference#evaluations): Detailed type signatures and interfaces +- [Events Guide](/SDKs/javascript/events): Monitor eval execution with event listeners +- [Schema Validation](/SDKs/javascript/schema-validation): Learn more about input/output validation +- [Core Concepts](/SDKs/javascript/core-concepts): Understanding agent architecture diff --git a/content/v1/Guides/events.mdx b/content/v1/Guides/events.mdx index 7d54ec80..b6368985 100644 --- a/content/v1/Guides/events.mdx +++ b/content/v1/Guides/events.mdx @@ -24,8 +24,8 @@ const agent = createAgent({ input: z.object({ task: z.string() }), output: z.object({ result: z.string() }) }, - handler: async (ctx, input) => { - ctx.logger.info('Processing task', { task: input.task }); + handler: async (c, input) => { + c.logger.info('Processing task', { task: input.task }); // Simulate processing await new Promise(resolve => setTimeout(resolve, 100)); @@ -36,35 +36,35 @@ const agent = createAgent({ // Track when agent starts agent.addEventListener('started', (eventName, agent, ctx) => { - ctx.state.set('startTime', Date.now()); - ctx.logger.info('Agent started', { + c.state.set('startTime', Date.now()); + c.logger.info('Agent started', { agentName: agent.metadata.name, - sessionId: ctx.sessionId + sessionId: c.sessionId }); }); // Track successful completion agent.addEventListener('completed', (eventName, agent, ctx) => { - const startTime = ctx.state.get('startTime') as number; + const startTime = c.state.get('startTime') as number; const duration = Date.now() - startTime; - ctx.logger.info('Agent completed', { + c.logger.info('Agent completed', { agentName: agent.metadata.name, duration, - sessionId: ctx.sessionId + sessionId: c.sessionId }); }); // Track errors agent.addEventListener('errored', (eventName, agent, ctx, error) => { - const startTime = ctx.state.get('startTime') as number; + const startTime = c.state.get('startTime') as number; const duration = Date.now() - startTime; - ctx.logger.error('Agent failed', { + c.logger.error('Agent failed', { agentName: agent.metadata.name, duration, error: error.message, - sessionId: ctx.sessionId + sessionId: c.sessionId }); }); @@ -74,7 +74,7 @@ export default agent; **Key Points:** - Event listeners receive the event name, agent instance, and context - `errored` listeners also receive the error object -- Use `ctx.state` to share data between event listeners +- Use `c.state` to share data between event listeners - Events execute sequentially in registration order ### Agent Validation with Events @@ -93,7 +93,7 @@ const agent = createAgent({ confidence: z.number() }) }, - handler: async (ctx, input) => { + handler: async (c, input) => { // Process query and return result return { answer: `Answer to: ${input.query}`, @@ -105,11 +105,11 @@ const agent = createAgent({ // Validate output quality in completed event agent.addEventListener('completed', (eventName, agent, ctx) => { // Access the validated output from context state - const output = ctx.state.get('_evalOutput') as { answer: string; confidence: number }; + const output = c.state.get('_evalOutput') as { answer: string; confidence: number }; // Check confidence threshold if (output.confidence < 0.7) { - ctx.logger.warn('Low confidence output detected', { + c.logger.warn('Low confidence output detected', { confidence: output.confidence, threshold: 0.7, agentName: agent.metadata.name @@ -118,7 +118,7 @@ agent.addEventListener('completed', (eventName, agent, ctx) => { // Check answer length if (output.answer.length < 10) { - ctx.logger.warn('Suspiciously short answer', { + c.logger.warn('Suspiciously short answer', { answerLength: output.answer.length, agentName: agent.metadata.name }); @@ -129,7 +129,7 @@ export default agent; ``` **Key Points:** -- Validated input/output stored in `ctx.state` as `_evalInput` and `_evalOutput` +- Validated input/output stored in `c.state` as `_evalInput` and `_evalOutput` - Event listeners should log warnings, not throw errors - Keep validation logic lightweight to avoid blocking @@ -166,14 +166,14 @@ app.addEventListener('agent.started', (eventName, agent, ctx) => { app.logger.info('Agent execution started', { agent: agentName, executionCount: count + 1, - session: ctx.sessionId + session: c.sessionId }); }); app.addEventListener('agent.completed', (eventName, agent, ctx) => { app.logger.info('Agent execution completed', { agent: agent.metadata.name, - session: ctx.sessionId + session: c.sessionId }); }); @@ -182,7 +182,7 @@ app.addEventListener('agent.errored', (eventName, agent, ctx, error) => { agent: agent.metadata.name, error: error.message, stack: error.stack, - session: ctx.sessionId + session: c.sessionId }); }); @@ -276,11 +276,11 @@ Clean up resources when threads are destroyed: import { createAgent } from '@agentuity/runtime'; const agent = createAgent({ - handler: async (ctx, input) => { + handler: async (c, input) => { // Register cleanup handler on first access - if (!ctx.thread.state.has('cleanupRegistered')) { - ctx.thread.addEventListener('destroyed', (eventName, thread) => { - ctx.logger.info('Cleaning up thread resources', { + if (!c.thread.state.has('cleanupRegistered')) { + c.thread.addEventListener('destroyed', (eventName, thread) => { + c.logger.info('Cleaning up thread resources', { threadId: thread.id, messageCount: thread.state.get('messageCount') || 0 }); @@ -289,12 +289,12 @@ const agent = createAgent({ thread.state.clear(); }); - ctx.thread.state.set('cleanupRegistered', true); + c.thread.state.set('cleanupRegistered', true); } // Track messages in this thread - const messageCount = (ctx.thread.state.get('messageCount') as number) || 0; - ctx.thread.state.set('messageCount', messageCount + 1); + const messageCount = (c.thread.state.get('messageCount') as number) || 0; + c.thread.state.set('messageCount', messageCount + 1); return { processed: true }; } @@ -320,10 +320,10 @@ const agent = createAgent({ schema: { input: z.object({ action: z.string() }) }, - handler: async (ctx, input) => { + handler: async (c, input) => { // Register session completion handler once - if (!ctx.session.state.has('persistenceRegistered')) { - ctx.session.addEventListener('completed', async (eventName, session) => { + if (!c.session.state.has('persistenceRegistered')) { + c.session.addEventListener('completed', async (eventName, session) => { // Save session metrics to KV storage const metrics = { totalRequests: session.state.get('totalRequests') || 0, @@ -331,24 +331,24 @@ const agent = createAgent({ duration: Date.now() - (session.state.get('startTime') as number || Date.now()) }; - await ctx.kv.set('session-metrics', session.id, metrics, { + await c.kv.set('session-metrics', session.id, metrics, { ttl: 86400 // 24 hours }); - ctx.logger.info('Session metrics saved', { + c.logger.info('Session metrics saved', { sessionId: session.id, metrics }); }); - ctx.session.state.set('persistenceRegistered', true); - ctx.session.state.set('startTime', Date.now()); + c.session.state.set('persistenceRegistered', true); + c.session.state.set('startTime', Date.now()); } // Track session activity - const totalRequests = (ctx.session.state.get('totalRequests') as number) || 0; - ctx.session.state.set('totalRequests', totalRequests + 1); - ctx.session.state.set('lastAction', input.action); + const totalRequests = (c.session.state.get('totalRequests') as number) || 0; + c.session.state.set('totalRequests', totalRequests + 1); + c.session.state.set('lastAction', input.action); return { success: true }; } @@ -372,7 +372,7 @@ Track agent execution time and identify slow operations: import { createAgent } from '@agentuity/runtime'; const agent = createAgent({ - handler: async (ctx, input) => { + handler: async (c, input) => { // Process request await new Promise(resolve => setTimeout(resolve, 1200)); return { result: 'completed' }; @@ -380,24 +380,24 @@ const agent = createAgent({ }); agent.addEventListener('started', (eventName, agent, ctx) => { - ctx.state.set('performanceStart', performance.now()); + c.state.set('performanceStart', performance.now()); }); agent.addEventListener('completed', (eventName, agent, ctx) => { - const startTime = ctx.state.get('performanceStart') as number; + const startTime = c.state.get('performanceStart') as number; const duration = performance.now() - startTime; // Log slow executions if (duration > 1000) { - ctx.logger.warn('Slow agent execution detected', { + c.logger.warn('Slow agent execution detected', { agentName: agent.metadata.name, duration, threshold: 1000, - sessionId: ctx.sessionId + sessionId: c.sessionId }); } - ctx.logger.info('Agent execution time', { + c.logger.info('Agent execution time', { agentName: agent.metadata.name, duration }); @@ -429,7 +429,7 @@ app.addEventListener('agent.errored', async (eventName, agent, ctx, error) => { errorType, errorMessage: error.message, errorCount: count + 1, - sessionId: ctx.sessionId, + sessionId: c.sessionId, stack: error.stack }); @@ -439,11 +439,11 @@ app.addEventListener('agent.errored', async (eventName, agent, ctx, error) => { agentName: agent.metadata.name, errorType, message: error.message, - sessionId: ctx.sessionId + sessionId: c.sessionId }; const errorKey = `error-${Date.now()}-${Math.random().toString(36).slice(2)}`; - await ctx.kv.set('error-logs', errorKey, errorLog, { + await c.kv.set('error-logs', errorKey, errorLog, { ttl: 604800 // 7 days }); }); @@ -467,7 +467,7 @@ app.addEventListener('agent.completed', async (eventName, agent, ctx) => { const metricsKey = `agent-metrics-${today}-${agentName}`; // Get current count - const result = await ctx.kv.get('analytics', metricsKey); + const result = await c.kv.get('analytics', metricsKey); let count = 0; if (result.exists) { @@ -476,7 +476,7 @@ app.addEventListener('agent.completed', async (eventName, agent, ctx) => { } // Increment and store - await ctx.kv.set('analytics', metricsKey, { + await c.kv.set('analytics', metricsKey, { count: count + 1, agentName, date: today @@ -518,17 +518,17 @@ app.addEventListener('agent.started', async (eventName, agent, ctx) => { event: 'agent.started', agentName: agent.metadata.name, agentId: agent.metadata.id, - sessionId: ctx.sessionId, + sessionId: c.sessionId, agentVersion: agent.metadata.version }; // Store in object storage for long-term retention - const logKey = `${auditLog.timestamp}-${ctx.sessionId}-${agent.metadata.name}.json`; + const logKey = `${auditLog.timestamp}-${c.sessionId}-${agent.metadata.name}.json`; - await ctx.objectstore.put('audit-logs', logKey, auditLog, { + await c.objectstore.put('audit-logs', logKey, auditLog, { contentType: 'application/json', metadata: { - sessionId: ctx.sessionId, + sessionId: c.sessionId, agentName: agent.metadata.name, timestamp: auditLog.timestamp } @@ -540,13 +540,13 @@ app.addEventListener('agent.completed', async (eventName, agent, ctx) => { timestamp: new Date().toISOString(), event: 'agent.completed', agentName: agent.metadata.name, - sessionId: ctx.sessionId + sessionId: c.sessionId // Note: Do not log full input/output for privacy }; - const logKey = `${auditLog.timestamp}-${ctx.sessionId}-${agent.metadata.name}-completed.json`; + const logKey = `${auditLog.timestamp}-${c.sessionId}-${agent.metadata.name}-completed.json`; - await ctx.objectstore.put('audit-logs', logKey, auditLog, { + await c.objectstore.put('audit-logs', logKey, auditLog, { contentType: 'application/json' }); }); @@ -568,7 +568,7 @@ See the [API Reference](/api-reference#key-value-storage) for complete documenta **Event Handler Guidelines** - **Keep handlers lightweight** - Event listeners should complete quickly to avoid blocking execution -- **Use `ctx.waitUntil()`** - For non-blocking background work, use `ctx.waitUntil()` to defer processing until after the response is sent +- **Use `c.waitUntil()`** - For non-blocking background work, use `c.waitUntil()` to defer processing until after the response is sent - **Don't modify request flow** - Event handlers should not throw errors to stop execution or modify the response - **Sequential execution** - Event listeners execute in registration order, one at a time - **Error handling** - Errors in event handlers are logged but don't stop execution or affect other listeners @@ -578,31 +578,31 @@ See the [API Reference](/api-reference#key-value-storage) for complete documenta ### Using waitUntil in Events -Use `ctx.waitUntil()` to perform heavy work without blocking the response: +Use `c.waitUntil()` to perform heavy work without blocking the response: ```typescript import { createAgent } from '@agentuity/runtime'; const agent = createAgent({ - handler: async (ctx, input) => { + handler: async (c, input) => { return { result: 'processed' }; } }); agent.addEventListener('completed', (eventName, agent, ctx) => { // Use waitUntil for non-blocking background work - ctx.waitUntil(async () => { + c.waitUntil(async () => { // Simulate sending metrics to external service await new Promise(resolve => setTimeout(resolve, 500)); - ctx.logger.info('Metrics sent to external service', { + c.logger.info('Metrics sent to external service', { agentName: agent.metadata.name, - sessionId: ctx.sessionId + sessionId: c.sessionId }); }); // This logs immediately - ctx.logger.info('Agent completed (handler finished)', { + c.logger.info('Agent completed (handler finished)', { agentName: agent.metadata.name }); }); @@ -651,7 +651,7 @@ export default app.server; // In agent.ts const agent = createAgent({ - handler: async (ctx, input) => { + handler: async (c, input) => { console.log(`[${Date.now()}] Agent: handler executing`); await new Promise(resolve => setTimeout(resolve, 100)); return { result: 'done' }; diff --git a/content/v1/Guides/key-value-storage.mdx b/content/v1/Guides/key-value-storage.mdx index b9b8513a..d8d1133c 100644 --- a/content/v1/Guides/key-value-storage.mdx +++ b/content/v1/Guides/key-value-storage.mdx @@ -19,14 +19,14 @@ Choose the right storage for your use case: ## Common Use Cases -- **Persistent User Data**: Store user profiles, "remember me" tokens, and preferences that persist beyond active session lifetimes (use `ctx.session.state` for active session data - see [Sessions and Threads](/Guides/sessions-threads)) +- **Persistent User Data**: Store user profiles, "remember me" tokens, and preferences that persist beyond active session lifetimes (use `c.session.state` for active session data - see [Sessions and Threads](/Guides/sessions-threads)) - **Configuration Storage**: Keep agent-specific settings, feature flags, and runtime configuration that can be updated without redeployment - **Caching**: Cache expensive computation results, API responses, or frequently accessed data to improve agent performance - **Inter-Agent Communication**: Share state between agents working together on complex workflows - **Rate Limiting**: Track API usage, request counts, and implement throttling mechanisms -v1 provides built-in state management (`ctx.state`, `ctx.thread.state`, `ctx.session.state`) for data tied to active requests, conversations, and sessions. Use KV when you need: +v1 provides built-in state management (`c.state`, `ctx.thread.state`, `c.session.state`) for data tied to active requests, conversations, and sessions. Use KV when you need: - **Custom TTL** longer than session/thread lifetimes - **Persistent data** across sessions (user profiles, settings) @@ -53,9 +53,9 @@ The SDK automatically creates key-value storage when you first access it: import { createAgent } from '@agentuity/runtime'; const agent = createAgent({ - handler: async (ctx, input) => { + handler: async (c, input) => { // Buckets are auto-created if they don't exist - await ctx.kv.set('user-sessions', 'user-123', { + await c.kv.set('user-sessions', 'user-123', { lastSeen: new Date().toISOString(), preferences: { theme: 'dark' } }); @@ -77,23 +77,23 @@ Store strings, objects, or binary data. Keys persist indefinitely by default, or ```typescript const agent = createAgent({ - handler: async (ctx, input) => { + handler: async (c, input) => { // Simple store - await ctx.kv.set('cache', 'api-response', responseData); + await c.kv.set('cache', 'api-response', responseData); // Store an object - await ctx.kv.set('user-prefs', input.userId, { + await c.kv.set('user-prefs', input.userId, { language: 'en', timezone: 'UTC' }); // Store with TTL (expires after 1 hour) - await ctx.kv.set('sessions', input.sessionId, userData, { + await c.kv.set('sessions', input.sessionId, userData, { ttl: 3600 // seconds }); // Store feature flags (no TTL - persistent config) - await ctx.kv.set('feature-flags', 'beta-features', { + await c.kv.set('feature-flags', 'beta-features', { darkMode: true, aiAssistant: false, newDashboard: true @@ -110,20 +110,20 @@ Retrieve stored values with automatic deserialization: ```typescript const agent = createAgent({ - handler: async (ctx, input) => { + handler: async (c, input) => { // Get a value - const result = await ctx.kv.get('user-prefs', input.userId); + const result = await c.kv.get('user-prefs', input.userId); if (result.exists) { // Data is already deserialized const preferences = result.data; - ctx.logger.info('User preferences:', preferences); + c.logger.info('User preferences:', preferences); } else { - ctx.logger.info('No preferences found for user'); + c.logger.info('No preferences found for user'); } // Handle missing keys gracefully - const configResult = await ctx.kv.get('config', 'app-settings'); + const configResult = await c.kv.get('config', 'app-settings'); const config = configResult.exists ? configResult.data : defaultConfig; return { config }; @@ -143,9 +143,9 @@ interface UserPreferences { } const agent = createAgent({ - handler: async (ctx, input) => { + handler: async (c, input) => { // Specify type for type-safe access - const result = await ctx.kv.get('user-prefs', input.userId); + const result = await c.kv.get('user-prefs', input.userId); if (result.exists) { // TypeScript knows the shape of result.data @@ -164,11 +164,11 @@ Remove keys when they're no longer needed: ```typescript const agent = createAgent({ - handler: async (ctx, input) => { + handler: async (c, input) => { // Delete a single key - await ctx.kv.delete('sessions', input.sessionId); + await c.kv.delete('sessions', input.sessionId); - ctx.logger.info('Session deleted successfully'); + c.logger.info('Session deleted successfully'); return { success: true }; } @@ -190,18 +190,18 @@ Always handle potential storage errors gracefully: ```typescript const agent = createAgent({ - handler: async (ctx, input) => { + handler: async (c, input) => { try { - const result = await ctx.kv.get('config', 'settings'); + const result = await c.kv.get('config', 'settings'); if (!result.exists) { // Initialize with defaults - await ctx.kv.set('config', 'settings', defaultSettings); + await c.kv.set('config', 'settings', defaultSettings); } return { config: result.exists ? result.data : defaultSettings }; } catch (error) { - ctx.logger.error('Storage error:', error); + c.logger.error('Storage error:', error); // Fall back to in-memory defaults return { config: defaultSettings }; @@ -224,25 +224,25 @@ Example with appropriate TTLs: ```typescript const agent = createAgent({ - handler: async (ctx, input) => { + handler: async (c, input) => { // Session data - expires after 24 hours - await ctx.kv.set('sessions', input.sessionId, sessionData, { + await c.kv.set('sessions', input.sessionId, sessionData, { ttl: 86400 }); // API cache - expires after 5 minutes - await ctx.kv.set('cache', cacheKey, apiResponse, { + await c.kv.set('cache', cacheKey, apiResponse, { ttl: 300 }); // Rate limit counter - expires at end of hour const secondsUntilHourEnd = 3600 - (Date.now() / 1000 % 3600); - await ctx.kv.set('rate-limits', input.userId, requestCount, { + await c.kv.set('rate-limits', input.userId, requestCount, { ttl: Math.ceil(secondsUntilHourEnd) }); // Feature flags - no TTL (persistent) - await ctx.kv.set('feature-flags', 'global', featureFlags); + await c.kv.set('feature-flags', 'global', featureFlags); return { success: true }; } @@ -272,6 +272,6 @@ TODO: Add video for v1 storage overview ## Next Steps -- [Agent Communication](/Guides/agent-communication) - Share state between agents -- [Sessions and Threads](/Guides/sessions-threads) - Manage conversation state -- [API Reference](/SDK/api-reference) - Complete storage API documentation +- [Agent Communication](/Guides/agent-communication): Share state between agents +- [Sessions and Threads](/Guides/sessions-threads): Manage conversation state +- [API Reference](/SDK/api-reference): Complete storage API documentation diff --git a/content/v1/Guides/object-storage.mdx b/content/v1/Guides/object-storage.mdx new file mode 100644 index 00000000..b32601fa --- /dev/null +++ b/content/v1/Guides/object-storage.mdx @@ -0,0 +1,529 @@ +--- +title: Object Storage +description: Durable file storage for documents, images, videos, and binary content +--- + +# Object Storage + +Object storage provides durable file storage for agents. Use it for documents, images, videos, and any binary content. + +## When to Use Object Storage + +Object storage is your solution for storing files, media, and large unstructured data that agents need to manage. Use it for documents, images, videos, backups, and any binary content. + +Choose the right storage for your use case: + +- **Object Storage**: Files, media, documents, backups +- **[Key-Value Storage](/Guides/key-value-storage)**: Fast lookups, session data, configuration +- **[Vector Storage](/Guides/vector-storage)**: Semantic search, embeddings, AI context + +**Note:** For temporary files that only need to exist during agent execution, use `ctx.state` instead of Object Storage. See [Sessions and Threads](/Guides/sessions-threads) for built-in state management. + +## Common Use Cases + +- **File Management**: Store user uploads, generated documents, and processed files +- **Media Storage**: Keep images, videos, audio files, and other media assets +- **Document Processing**: Store PDFs, spreadsheets, and documents for agent processing +- **Backup and Archive**: Maintain backups of agent-generated content or historical data +- **Static Asset Serving**: Host files that can be accessed via public URLs +- **Data Export**: Store generated reports, exports, and downloadable content + +## Creating Object Storage + +You can create object storage buckets either through the Cloud Console or programmatically in your agent code. + +### Via Cloud Console + +Navigate to **Services > Object Store** and click **Create Storage**. Choose a descriptive bucket name that reflects its purpose (e.g., `user-uploads`, `processed-documents`, `media-assets`). + + + +### Via SDK + +Buckets are automatically created when you first access them: + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const agent = createAgent({ + handler: async (c, input) => { + // TODO: Verify file upload input handling pattern + const imageData = new Uint8Array(input.fileData); + + // Bucket 'user-uploads' is auto-created if it doesn't exist + await c.objectstore.put('user-uploads', 'profile-123.jpg', imageData, { + contentType: 'image/jpeg' + }); + + return { message: 'Image uploaded successfully' }; + } +}); +``` + +## Working with Object Storage + +The object storage API provides four core operations: `get`, `put`, `delete`, and `createPublicURL`. All operations are asynchronous and support various content types. + +### Storing Objects + +Store files with optional metadata and HTTP headers: + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const agent = createAgent({ + handler: async (c, input) => { + // Store with content type + await c.objectstore.put('documents', 'report.pdf', pdfData, { + contentType: 'application/pdf' + }); + + // Store with full metadata + await c.objectstore.put('uploads', 'document.pdf', pdfData, { + contentType: 'application/pdf', + contentDisposition: 'attachment; filename="report.pdf"', + cacheControl: 'max-age=3600', + metadata: { + 'uploaded-by': input.userId, + 'processed': 'false' + } + }); + + // Store text file + const textData = new TextEncoder().encode(logContent); + await c.objectstore.put('logs', 'agent.log', textData, { + contentType: 'text/plain', + contentEncoding: 'utf-8' + }); + + return { success: true }; + } +}); +``` + +**Put Parameters:** +- `contentType` (optional): MIME type of the file (default: `application/octet-stream`) +- `contentDisposition` (optional): Controls how browsers handle the file (download vs display) +- `cacheControl` (optional): Browser caching behavior +- `contentEncoding` (optional): Content encoding (e.g., `gzip`, `utf-8`) +- `contentLanguage` (optional): Content language (e.g., `en-US`) +- `metadata` (optional): Custom metadata as key-value pairs + +**Data Types:** +The `put` method accepts: +- `Uint8Array` - Binary data +- `ArrayBuffer` - Raw binary buffer +- `ReadableStream` - Streaming data + +### Retrieving Objects + +Retrieve stored objects with type-safe null checking: + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const agent = createAgent({ + handler: async (c, input) => { + // Get an object + const result = await c.objectstore.get('documents', 'report.pdf'); + + if (result.exists) { + // Access binary data (Uint8Array) + const pdfData = result.data; + const contentType = result.contentType; + + c.logger.info(`PDF size: ${pdfData.byteLength} bytes`); + c.logger.info(`Content type: ${contentType}`); + } else { + c.logger.info('Object not found'); + } + + // Convert to text if needed + const textResult = await c.objectstore.get('logs', 'agent.log'); + if (textResult.exists) { + const logContent = new TextDecoder().decode(textResult.data); + c.logger.info('Log content:', logContent); + } + + return { found: result.exists }; + } +}); +``` + +**Get Return Value:** +Returns a discriminated union for type-safe null checking: +- `{ exists: true, data: Uint8Array, contentType: string }` - Object found +- `{ exists: false }` - Object not found + +**Converting Data:** +- Binary data is returned as `Uint8Array` +- Use `TextDecoder` to convert to string: `new TextDecoder().decode(data)` +- Use `TextEncoder` to convert string to Uint8Array: `new TextEncoder().encode(text)` + +### Generating Public URLs + +Create time-limited public URLs for direct access to objects: + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const agent = createAgent({ + handler: async (c, input) => { + // Create a 1-hour public URL + const publicUrl = await c.objectstore.createPublicURL( + 'documents', + 'report.pdf', + { expiresDuration: 3600000 } // 1 hour in milliseconds + ); + + c.logger.info(`Download link: ${publicUrl}`); + + // Create with default expiration (1 hour) + const imageUrl = await c.objectstore.createPublicURL('images', 'photo.jpg'); + + // Create short-lived URL (minimum 1 minute) + const tempUrl = await c.objectstore.createPublicURL( + 'temp-files', + 'preview.png', + { expiresDuration: 60000 } + ); + + return { publicUrl, imageUrl, tempUrl }; + } +}); +``` + +**CreatePublicURL Parameters:** +- `expiresDuration` (optional): Duration in milliseconds (default: 1 hour, minimum: 1 minute) + +**Return Value:** +- Returns a string containing the signed public URL +- Throws an error if the object doesn't exist + +### Deleting Objects + +Remove objects when they're no longer needed: + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const agent = createAgent({ + handler: async (c, input) => { + // Delete an object + const wasDeleted = await c.objectstore.delete('temp-files', 'processing.tmp'); + + if (wasDeleted) { + c.logger.info('Temporary file cleaned up'); + } else { + c.logger.info('File was already deleted'); + } + + return { deleted: wasDeleted }; + } +}); +``` + +**Delete Return Value:** +- Returns `true` if the object was deleted +- Returns `false` if the object didn't exist + +## Best Practices + +### Bucket Organization + +Structure your buckets by purpose and access patterns: +- `user-uploads`: User-submitted content +- `processed-output`: Agent-generated results +- `public-assets`: Files meant for public access +- `temp-storage`: Short-lived processing files + +### Key Naming Conventions + +Use hierarchical paths for better organization: +- `users/{userId}/profile.jpg` +- `documents/{year}/{month}/report-{id}.pdf` +- `exports/{timestamp}/data.csv` + +```typescript +const agent = createAgent({ + handler: async (c, input) => { + // Organize by user + const key = `users/${input.userId}/profile.jpg`; + await c.objectstore.put('uploads', key, imageData, { + contentType: 'image/jpeg' + }); + + // Organize by date + const date = new Date(); + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const documentKey = `documents/${year}/${month}/report-${input.reportId}.pdf`; + + await c.objectstore.put('reports', documentKey, pdfData, { + contentType: 'application/pdf' + }); + + return { success: true }; + } +}); +``` + +### Content Type Management + +Always set appropriate content types for better browser handling: + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const contentTypes: Record = { + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.png': 'image/png', + '.gif': 'image/gif', + '.pdf': 'application/pdf', + '.json': 'application/json', + '.csv': 'text/csv', + '.txt': 'text/plain', + '.html': 'text/html', + '.xml': 'application/xml' +}; + +const agent = createAgent({ + handler: async (c, input) => { + const filename = input.filename; + const extension = filename.substring(filename.lastIndexOf('.')); + const contentType = contentTypes[extension] || 'application/octet-stream'; + + await c.objectstore.put('uploads', filename, input.fileData, { + contentType + }); + + return { success: true, contentType }; + } +}); +``` + +### Public URL Security + +Use appropriate expiration times for public URLs: + +```typescript +const agent = createAgent({ + handler: async (c, input) => { + let expiresDuration: number; + + switch (input.accessType) { + case 'temporary': + // Temporary downloads: 5-15 minutes + expiresDuration = 15 * 60 * 1000; // 15 minutes + break; + case 'shared': + // Shared documents: 1-24 hours + expiresDuration = 24 * 60 * 60 * 1000; // 24 hours + break; + case 'quick': + // Quick preview: 1-5 minutes + expiresDuration = 5 * 60 * 1000; // 5 minutes + break; + default: + // Default: 1 hour + expiresDuration = 60 * 60 * 1000; + } + + const url = await c.objectstore.createPublicURL( + 'documents', + input.documentKey, + { expiresDuration } + ); + + return { url, expiresIn: expiresDuration }; + } +}); +``` + +**Important:** Never create permanent public URLs for sensitive data. Always use time-limited signed URLs. + +### Error Handling + +Handle storage operations gracefully: + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const agent = createAgent({ + handler: async (c, input) => { + try { + // Attempt to store file + await c.objectstore.put('uploads', input.key, input.data, { + contentType: input.contentType + }); + + // Generate temporary URL for response + const url = await c.objectstore.createPublicURL( + 'uploads', + input.key, + { expiresDuration: 900000 } // 15 minutes + ); + + return { + success: true, + url + }; + } catch (error) { + c.logger.error('Storage error:', error); + + return { + success: false, + error: 'Failed to process file' + }; + } + } +}); +``` + +## File Processing Patterns + +### Upload Handler + +Create a robust file upload handler: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +// TODO: Verify the correct pattern for handling file uploads in v1 +// This example assumes file data comes through the input parameter +const uploadAgent = createAgent({ + schema: { + input: z.object({ + filename: z.string(), + contentType: z.string(), + fileData: z.instanceof(Uint8Array) + }), + output: z.object({ + message: z.string(), + key: z.string(), + url: z.string() + }) + }, + handler: async (c, input) => { + // Generate unique key + const timestamp = Date.now(); + const key = `uploads/${timestamp}-${input.filename}`; + + // Store the uploaded file + await c.objectstore.put('user-files', key, input.fileData, { + contentType: input.contentType, + metadata: { + 'original-name': input.filename, + 'upload-time': new Date().toISOString() + } + }); + + // Generate access URL (1 hour expiration) + const url = await c.objectstore.createPublicURL( + 'user-files', + key, + { expiresDuration: 3600000 } + ); + + return { + message: 'File uploaded successfully', + key, + url + }; + } +}); +``` + +### Document Processing Pipeline + +Process and store documents with metadata tracking: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const documentProcessorAgent = createAgent({ + schema: { + input: z.object({ + documentId: z.string(), + rawDocument: z.instanceof(Uint8Array) + }) + }, + handler: async (c, input) => { + // Store raw document + const rawKey = `documents/raw/${input.documentId}.pdf`; + await c.objectstore.put('documents', rawKey, input.rawDocument, { + contentType: 'application/pdf', + metadata: { + 'status': 'raw', + 'document-id': input.documentId, + 'uploaded-at': new Date().toISOString() + } + }); + + // Process document (example: convert to text) + const processedText = await processDocument(input.rawDocument); + const textData = new TextEncoder().encode(processedText); + + // Store processed version + const processedKey = `documents/processed/${input.documentId}.txt`; + await c.objectstore.put('documents', processedKey, textData, { + contentType: 'text/plain', + metadata: { + 'status': 'processed', + 'document-id': input.documentId, + 'processed-at': new Date().toISOString() + } + }); + + // Generate download URLs + const rawUrl = await c.objectstore.createPublicURL( + 'documents', + rawKey, + { expiresDuration: 3600000 } + ); + + const processedUrl = await c.objectstore.createPublicURL( + 'documents', + processedKey, + { expiresDuration: 3600000 } + ); + + return { + documentId: input.documentId, + rawUrl, + processedUrl + }; + } +}); + +// Helper function (example) +async function processDocument(pdfData: Uint8Array): Promise { + // Implement document processing logic + return 'Extracted text from document'; +} +``` + +## Monitoring Usage + +Track your object storage usage through the Cloud Console: + +1. Navigate to **Services > Object Store** +2. View storage size and object count for each bucket +3. Monitor provider information and creation dates +4. Track storage operations through agent telemetry + +For structured data with complex queries, consider using object storage to store data exports while maintaining indexes in key-value or vector storage. + +## Storage Types Overview + +TODO: Add video for v1 storage overview + +## Next Steps + +- [Key-Value Storage](/Guides/key-value-storage): Fast lookups for simple data +- [Vector Storage](/Guides/vector-storage): Semantic search and embeddings +- [Sessions and Threads](/Guides/sessions-threads): Manage conversation state +- [API Reference](/SDK/api-reference): Complete storage API documentation diff --git a/content/v1/Guides/routing-triggers.mdx b/content/v1/Guides/routing-triggers.mdx index cd7adbec..24599da0 100644 --- a/content/v1/Guides/routing-triggers.mdx +++ b/content/v1/Guides/routing-triggers.mdx @@ -352,7 +352,7 @@ const agent = createAgent({ input: z.object({ message: z.string() }), output: z.string() }, - handler: async (ctx, input) => { + handler: async (c, input) => { return `Received: ${input.message}`; } }); diff --git a/content/v1/Guides/schema-validation.mdx b/content/v1/Guides/schema-validation.mdx index eba83ae8..a6d2221f 100644 --- a/content/v1/Guides/schema-validation.mdx +++ b/content/v1/Guides/schema-validation.mdx @@ -76,10 +76,10 @@ const agent = createAgent({ }).optional(), }), }, - handler: async (ctx, input) => { + handler: async (c, input) => { // input is validated before this runs // TypeScript knows the exact shape of input - ctx.logger.info(`User email: ${input.email}`); + c.logger.info(`User email: ${input.email}`); return { success: true }; }, }); @@ -98,7 +98,7 @@ const agent = createAgent({ status: z.enum(['active', 'pending']), }), }, - handler: async (ctx, input) => { + handler: async (c, input) => { return { userId: crypto.randomUUID(), created: new Date(), @@ -125,7 +125,7 @@ const agent = createAgent({ total: z.number(), }), }, - handler: async (ctx, input) => { + handler: async (c, input) => { // Both input and output are validated return { results: ['item1', 'item2'], @@ -142,7 +142,7 @@ Schemas are optional. If not provided, no validation occurs: ```typescript const agent = createAgent({ // No schema defined - handler: async (ctx, input) => { + handler: async (c, input) => { // input is unknown type // No runtime validation return { message: 'Done' }; @@ -169,7 +169,7 @@ const agent = createAgent({ }), }), }, - handler: async (ctx, input) => { + handler: async (c, input) => { // TypeScript knows: // - input.query is string // - input.filters.category is 'tech' | 'business' | 'sports' @@ -195,7 +195,7 @@ const agent = createAgent({ total: z.number(), }), }, - handler: async (ctx, input) => { + handler: async (c, input) => { // Return type is validated and type-checked return { results: [ @@ -216,7 +216,7 @@ When calling agents, TypeScript knows the input and output types: ```typescript // When calling the agent from another agent: -const result = await ctx.agent.searchAgent.run({ +const result = await c.agent.searchAgent.run({ query: 'agentic AI', filters: { category: 'tech', limit: 5 }, }); @@ -406,9 +406,9 @@ const agent = createAgent({ count: z.number(), }), }, - handler: async (ctx, input) => { + handler: async (c, input) => { // Input validated before handler runs - ctx.logger.info('Processing query', { query: input.query }); + c.logger.info('Processing query', { query: input.query }); // Process the query... const results = [ @@ -486,7 +486,7 @@ When validation fails, an error is thrown with detailed information: ```typescript try { - await ctx.agent.userAgent.run({ + await c.agent.userAgent.run({ email: 'invalid-email', // Invalid format age: -5, // Invalid range }); @@ -508,14 +508,14 @@ const agent = createAgent({ schema: { input: z.object({ age: z.number().min(0) }), }, - handler: async (ctx, input) => { + handler: async (c, input) => { // This never runs if age is negative return { processed: true }; }, }); // This throws a validation error: -await ctx.agent.myAgent.run({ age: -1 }); +await c.agent.myAgent.run({ age: -1 }); ``` ### Output Validation Errors @@ -527,7 +527,7 @@ const agent = createAgent({ schema: { output: z.object({ status: z.enum(['ok', 'error']) }), }, - handler: async (ctx, input) => { + handler: async (c, input) => { return { status: 'invalid' }; // Validation error! }, }); @@ -634,7 +634,7 @@ const agent = createAgent({ age: z.number().min(0) }) }, - handler: async (ctx, input) => { + handler: async (c, input) => { // input is guaranteed valid }, }); diff --git a/content/v1/Guides/sessions-threads.mdx b/content/v1/Guides/sessions-threads.mdx index d1610b1c..2b5e823c 100644 --- a/content/v1/Guides/sessions-threads.mdx +++ b/content/v1/Guides/sessions-threads.mdx @@ -3,7 +3,7 @@ title: Sessions & Threads description: Stateful context management for conversational agents --- -Sessions and threads provide stateful context management at three different scopes: request-level for temporary data, thread-level for conversation context lasting up to 1 hour, and session-level for user data spanning multiple conversations. Use request state (`ctx.state`) for timing and calculations, thread state (`ctx.thread.state`) for chatbot memory, and session state (`ctx.session.state`) for user preferences and cross-conversation tracking. +Sessions and threads provide stateful context management at three different scopes: request-level for temporary data, thread-level for conversation context lasting up to 1 hour, and session-level for user data spanning multiple conversations. Use request state (`c.state`) for timing and calculations, thread state (`c.thread.state`) for chatbot memory, and session state (`c.session.state`) for user preferences and cross-conversation tracking. For event-based lifecycle management of sessions and threads, see the [Events Guide](/Guides/events). @@ -11,9 +11,9 @@ For event-based lifecycle management of sessions and threads, see the [Events Gu The SDK provides three distinct state scopes, each with different lifetimes and use cases: -- **Request state** (`ctx.state`) - Temporary data within a single request, cleared after response -- **Thread state** (`ctx.thread.state`) - Conversation context across multiple requests, expires after 1 hour -- **Session state** (`ctx.session.state`) - User-level data spanning multiple threads and conversations +- **Request state** (`c.state`) - Temporary data within a single request, cleared after response +- **Thread state** (`c.thread.state`) - Conversation context across multiple requests, expires after 1 hour +- **Session state** (`c.session.state`) - User-level data spanning multiple threads and conversations ### Three State Scopes in Action @@ -33,22 +33,22 @@ const agent = createAgent({ totalUserRequests: z.number() }) }, - handler: async (ctx, input) => { + handler: async (c, input) => { // REQUEST STATE: Temporary data for this request only - ctx.state.set('requestStart', Date.now()); + c.state.set('requestStart', Date.now()); // THREAD STATE: Conversation history (persists up to 1 hour) - const messages = (ctx.thread.state.get('messages') as string[]) || []; + const messages = (c.thread.state.get('messages') as string[]) || []; messages.push(input.message); - ctx.thread.state.set('messages', messages); + c.thread.state.set('messages', messages); // SESSION STATE: User-level data (persists across threads) - const totalRequests = (ctx.session.state.get('totalRequests') as number) || 0; - ctx.session.state.set('totalRequests', totalRequests + 1); - ctx.session.state.set('lastMessage', input.message); + const totalRequests = (c.session.state.get('totalRequests') as number) || 0; + c.session.state.set('totalRequests', totalRequests + 1); + c.session.state.set('lastMessage', input.message); // Request state is used for response calculation - const requestTime = Date.now() - (ctx.state.get('requestStart') as number); + const requestTime = Date.now() - (c.state.get('requestStart') as number); return { response: `Received: ${input.message}`, @@ -74,9 +74,9 @@ export default agent; | Scope | Lifetime | Cleared When | Use Case | Access | |-------|----------|--------------|----------|--------| -| Request | Single request | After response sent | Timing, temp calculations | `ctx.state` | -| Thread | Up to 1 hour | Thread expiration or destroy | Conversation history | `ctx.thread.state` | -| Session | Spans threads | In-memory (provider dependent) | User preferences | `ctx.session.state` | +| Request | Single request | After response sent | Timing, temp calculations | `c.state` | +| Thread | Up to 1 hour | Thread expiration or destroy | Conversation history | `c.thread.state` | +| Session | Spans threads | In-memory (provider dependent) | User preferences | `c.session.state` | ## Thread Management @@ -99,7 +99,7 @@ Threads represent conversation contexts with a 1-hour lifetime. Each thread is i - `destroyed` event fires when thread expires **Manual Destroy:** -- Call `await ctx.thread.destroy()` to reset the conversation +- Call `await c.thread.destroy()` to reset the conversation - Useful for starting new conversations or clearing context ### Conversation Context with Threads @@ -122,37 +122,37 @@ const agent = createAgent({ source: z.enum(['kv', 'thread-state', 'new']) }) }, - handler: async (ctx, input) => { + handler: async (c, input) => { // Reset conversation if requested if (input.reset) { - await ctx.thread.destroy(); + await c.thread.destroy(); } - const conversationKey = `conversation_${ctx.thread.id}`; + const conversationKey = `conversation_${c.thread.id}`; let source: 'kv' | 'thread-state' | 'new' = 'new'; // Load conversation from KV on first access - if (!ctx.thread.state.has('messages')) { - const result = await ctx.kv.get('conversations', conversationKey); + if (!c.thread.state.has('messages')) { + const result = await c.kv.get('conversations', conversationKey); if (result.exists) { const saved = await result.data.json(); - ctx.thread.state.set('messages', saved.messages); + c.thread.state.set('messages', saved.messages); source = 'kv'; - ctx.logger.info('Loaded conversation from KV', { - threadId: ctx.thread.id, + c.logger.info('Loaded conversation from KV', { + threadId: c.thread.id, messageCount: saved.messages.length }); } else { - ctx.thread.state.set('messages', []); + c.thread.state.set('messages', []); } // Register save handler when thread is destroyed - ctx.thread.addEventListener('destroyed', async (eventName, thread) => { + c.thread.addEventListener('destroyed', async (eventName, thread) => { const messages = thread.state.get('messages') as string[]; if (messages && messages.length > 0) { - await ctx.kv.set('conversations', conversationKey, { + await c.kv.set('conversations', conversationKey, { threadId: thread.id, messages, savedAt: new Date().toISOString() @@ -160,7 +160,7 @@ const agent = createAgent({ ttl: 86400 // Keep for 24 hours }); - ctx.logger.info('Saved conversation to KV', { + c.logger.info('Saved conversation to KV', { threadId: thread.id, messageCount: messages.length }); @@ -171,9 +171,9 @@ const agent = createAgent({ } // Add message to thread state (fast access) - const messages = ctx.thread.state.get('messages') as string[]; + const messages = c.thread.state.get('messages') as string[]; messages.push(input.message); - ctx.thread.state.set('messages', messages); + c.thread.state.set('messages', messages); return { response: `Stored message ${messages.length}`, @@ -200,11 +200,11 @@ Sessions represent individual request executions with unique session IDs. While **Creation:** - A new session is created for each request -- Each session has a unique `sessionId` accessible via `ctx.sessionId` -- Sessions belong to a thread: `ctx.session.thread` +- Each session has a unique `sessionId` accessible via `c.sessionId` +- Sessions belong to a thread: `c.session.thread` **State:** -- `ctx.session.state` persists across threads for the same user +- `c.session.state` persists across threads for the same user - Use for user preferences, settings, cross-conversation data - State stored in memory (persistence depends on SessionProvider) @@ -225,11 +225,11 @@ Clean up resources when threads are destroyed (either by expiration or manual de import { createAgent } from '@agentuity/runtime'; const agent = createAgent({ - handler: async (ctx, input) => { + handler: async (c, input) => { // Register cleanup handler once per thread - if (!ctx.thread.state.has('cleanupRegistered')) { - ctx.thread.addEventListener('destroyed', async (eventName, thread) => { - ctx.logger.info('Thread destroyed - cleaning up', { + if (!c.thread.state.has('cleanupRegistered')) { + c.thread.addEventListener('destroyed', async (eventName, thread) => { + c.logger.info('Thread destroyed - cleaning up', { threadId: thread.id, messageCount: thread.state.get('messageCount') || 0, conversationDuration: Date.now() - (thread.state.get('startTime') as number || Date.now()) @@ -238,7 +238,7 @@ const agent = createAgent({ // Save conversation summary to KV before cleanup const messages = thread.state.get('messages') as string[] || []; if (messages.length > 0) { - await ctx.kv.set('conversation-summaries', thread.id, { + await c.kv.set('conversation-summaries', thread.id, { messageCount: messages.length, lastMessages: messages.slice(-5), endedAt: new Date().toISOString() @@ -251,17 +251,17 @@ const agent = createAgent({ thread.state.clear(); }); - ctx.thread.state.set('cleanupRegistered', true); - ctx.thread.state.set('startTime', Date.now()); + c.thread.state.set('cleanupRegistered', true); + c.thread.state.set('startTime', Date.now()); } // Track messages - const messageCount = (ctx.thread.state.get('messageCount') as number) || 0; - ctx.thread.state.set('messageCount', messageCount + 1); + const messageCount = (c.thread.state.get('messageCount') as number) || 0; + c.thread.state.set('messageCount', messageCount + 1); - const messages = (ctx.thread.state.get('messages') as string[]) || []; + const messages = (c.thread.state.get('messages') as string[]) || []; messages.push(JSON.stringify(input)); - ctx.thread.state.set('messages', messages); + c.thread.state.set('messages', messages); return { processed: true, messageCount: messageCount + 1 }; } @@ -303,24 +303,24 @@ const agent = createAgent({ }), stream: true }, - handler: async (ctx, input) => { - const conversationKey = `chat_${ctx.thread.id}`; + handler: async (c, input) => { + const conversationKey = `chat_${c.thread.id}`; // Load conversation history from KV let messages: CoreMessage[] = []; try { - const result = await ctx.kv.get('conversations', conversationKey); + const result = await c.kv.get('conversations', conversationKey); if (result.exists) { messages = await result.data.json() as CoreMessage[]; - ctx.logger.info('Loaded conversation from KV', { - threadId: ctx.thread.id, + c.logger.info('Loaded conversation from KV', { + threadId: c.thread.id, messageCount: messages.length }); } } catch (error) { - ctx.logger.error('Error loading conversation history', { error }); + c.logger.error('Error loading conversation history', { error }); } // Add user message to conversation @@ -337,7 +337,7 @@ const agent = createAgent({ }); // Save conversation after stream completes (non-blocking) - ctx.waitUntil(async () => { + c.waitUntil(async () => { try { const fullText = await result.text; @@ -351,16 +351,16 @@ const agent = createAgent({ const recentMessages = messages.slice(-20); // Save to KV storage - await ctx.kv.set('conversations', conversationKey, recentMessages, { + await c.kv.set('conversations', conversationKey, recentMessages, { ttl: 86400 // 24 hours }); - ctx.logger.info('Saved conversation to KV', { - threadId: ctx.thread.id, + c.logger.info('Saved conversation to KV', { + threadId: c.thread.id, messageCount: recentMessages.length }); } catch (error) { - ctx.logger.error('Error saving conversation history', { error }); + c.logger.error('Error saving conversation history', { error }); } }); @@ -406,18 +406,18 @@ const agent = createAgent({ source: z.enum(['cache', 'storage', 'default']) }) }, - handler: async (ctx, input) => { + handler: async (c, input) => { const userId = input.userId; let source: 'cache' | 'storage' | 'default' = 'default'; // Check session state cache first - let preferences = ctx.session.state.get(`preferences_${userId}`) as z.infer | undefined; + let preferences = c.session.state.get(`preferences_${userId}`) as z.infer | undefined; if (preferences) { source = 'cache'; } else { // Load from KV storage - const result = await ctx.kv.get('user-preferences', userId); + const result = await c.kv.get('user-preferences', userId); if (result.exists) { preferences = await result.data.json(); @@ -433,16 +433,16 @@ const agent = createAgent({ } // Cache in session state - ctx.session.state.set(`preferences_${userId}`, preferences); + c.session.state.set(`preferences_${userId}`, preferences); } // Update if requested if (input.updatePreferences) { preferences = { ...preferences, ...input.updatePreferences }; - ctx.session.state.set(`preferences_${userId}`, preferences); + c.session.state.set(`preferences_${userId}`, preferences); // Persist to KV storage - await ctx.kv.set('user-preferences', userId, preferences, { + await c.kv.set('user-preferences', userId, preferences, { ttl: 2592000 // 30 days }); } @@ -488,14 +488,14 @@ const agent = createAgent({ complete: z.boolean() }) }, - handler: async (ctx, input) => { + handler: async (c, input) => { // Reset workflow if requested if (input.reset) { - ctx.thread.state.delete('workflowState'); + c.thread.state.delete('workflowState'); } // Initialize workflow - let workflowState = ctx.thread.state.get('workflowState') as z.infer | undefined; + let workflowState = c.thread.state.get('workflowState') as z.infer | undefined; if (!workflowState) { workflowState = { @@ -541,7 +541,7 @@ const agent = createAgent({ complete = true; // Save to storage - await ctx.kv.set('registrations', workflowState.data.email!, workflowState.data, { + await c.kv.set('registrations', workflowState.data.email!, workflowState.data, { ttl: 86400 }); } else { @@ -556,7 +556,7 @@ const agent = createAgent({ } // Save workflow state - ctx.thread.state.set('workflowState', workflowState); + c.thread.state.set('workflowState', workflowState); return { message, @@ -577,12 +577,12 @@ Implement rate limiting per session: import { createAgent } from '@agentuity/runtime'; const agent = createAgent({ - handler: async (ctx, input) => { + handler: async (c, input) => { const requestLimit = 10; const windowMs = 60000; // 1 minute // Track requests in session state - const requestLog = (ctx.session.state.get('requestLog') as Array) || []; + const requestLog = (c.session.state.get('requestLog') as Array) || []; const now = Date.now(); // Remove requests outside the time window @@ -593,8 +593,8 @@ const agent = createAgent({ const oldestRequest = Math.min(...recentRequests); const resetIn = windowMs - (now - oldestRequest); - ctx.logger.warn('Rate limit exceeded', { - sessionId: ctx.sessionId, + c.logger.warn('Rate limit exceeded', { + sessionId: c.sessionId, requestCount: recentRequests.length, resetInMs: resetIn }); @@ -609,7 +609,7 @@ const agent = createAgent({ // Add current request recentRequests.push(now); - ctx.session.state.set('requestLog', recentRequests); + c.session.state.set('requestLog', recentRequests); return { processed: true, @@ -660,15 +660,15 @@ const agent = createAgent({ profile: UserProfileSchema }) }, - handler: async (ctx, input) => { + handler: async (c, input) => { const userId = input.userId; // Load profile from session state cache - let profile = ctx.session.state.get(`profile_${userId}`) as z.infer | undefined; + let profile = c.session.state.get(`profile_${userId}`) as z.infer | undefined; // If not in cache, load from KV if (!profile) { - const result = await ctx.kv.get('user-profiles', userId); + const result = await c.kv.get('user-profiles', userId); if (result.exists) { profile = await result.data.json(); @@ -686,23 +686,23 @@ const agent = createAgent({ } // Cache in session state - ctx.session.state.set(`profile_${userId}`, profile); + c.session.state.set(`profile_${userId}`, profile); // Register session completion handler to save on exit - if (!ctx.session.state.has('saveHandlerRegistered')) { - ctx.session.addEventListener('completed', async (eventName, session) => { + if (!c.session.state.has('saveHandlerRegistered')) { + c.session.addEventListener('completed', async (eventName, session) => { const profileToSave = session.state.get(`profile_${userId}`) as typeof profile; if (profileToSave) { - await ctx.kv.set('user-profiles', userId, profileToSave, { + await c.kv.set('user-profiles', userId, profileToSave, { ttl: 2592000 // 30 days }); - ctx.logger.info('Saved user profile', { userId }); + c.logger.info('Saved user profile', { userId }); } }); - ctx.session.state.set('saveHandlerRegistered', true); + c.session.state.set('saveHandlerRegistered', true); } } @@ -710,7 +710,7 @@ const agent = createAgent({ if (input.updateProfile) { profile = { ...profile, ...input.updateProfile }; profile.lastActive = new Date().toISOString(); - ctx.session.state.set(`profile_${userId}`, profile); + c.session.state.set(`profile_${userId}`, profile); } return { profile }; @@ -744,12 +744,12 @@ Keep conversation history bounded to avoid memory issues: import { createAgent } from '@agentuity/runtime'; const agent = createAgent({ - handler: async (ctx, input) => { + handler: async (c, input) => { const maxMessages = 50; // Keep last 50 messages in memory const archiveThreshold = 40; // Archive when reaching 40 messages // Get current messages from thread state - const messages = (ctx.thread.state.get('messages') as string[]) || []; + const messages = (c.thread.state.get('messages') as string[]) || []; // Add new message messages.push(JSON.stringify(input)); @@ -760,26 +760,26 @@ const agent = createAgent({ if (toArchive.length > 0) { // Archive to KV storage - const archiveKey = `archive_${ctx.thread.id}_${Date.now()}`; - await ctx.kv.set('message-archives', archiveKey, { - threadId: ctx.thread.id, + const archiveKey = `archive_${c.thread.id}_${Date.now()}`; + await c.kv.set('message-archives', archiveKey, { + threadId: c.thread.id, messages: toArchive, archivedAt: new Date().toISOString() }, { ttl: 604800 // Keep for 7 days }); - ctx.logger.info('Archived old messages', { - threadId: ctx.thread.id, + c.logger.info('Archived old messages', { + threadId: c.thread.id, archivedCount: toArchive.length }); } // Keep only recent messages in state const recentMessages = messages.slice(-maxMessages); - ctx.thread.state.set('messages', recentMessages); + c.thread.state.set('messages', recentMessages); } else { - ctx.thread.state.set('messages', messages); + c.thread.state.set('messages', messages); } return { @@ -802,14 +802,14 @@ export default agent; Thread IDs and session IDs serve different purposes in the SDK: -**Thread ID (`ctx.thread.id`):** +**Thread ID (`c.thread.id`):** - Represents a conversation or interaction sequence - Format: `thrd_<32-char-hex>` - Stored in cookie `atid` for client persistence - Same thread ID across multiple requests (up to 1 hour) - Use for grouping related requests into conversations -**Session ID (`ctx.sessionId`):** +**Session ID (`c.sessionId`):** - Represents a single request execution - Format: `sess_<32-char-hex>` (or similar) - Unique for each request @@ -824,28 +824,28 @@ Demonstrate the relationship between thread and session IDs: import { createAgent } from '@agentuity/runtime'; const agent = createAgent({ - handler: async (ctx, input) => { + handler: async (c, input) => { // Track unique sessions per thread - const sessionsSeen = (ctx.thread.state.get('sessionsSeen') as Set) || new Set(); - sessionsSeen.add(ctx.sessionId); - ctx.thread.state.set('sessionsSeen', sessionsSeen); + const sessionsSeen = (c.thread.state.get('sessionsSeen') as Set) || new Set(); + sessionsSeen.add(c.sessionId); + c.thread.state.set('sessionsSeen', sessionsSeen); // Log both IDs for correlation - ctx.logger.info('Request tracking', { - threadId: ctx.thread.id, // Same across conversation - sessionId: ctx.sessionId, // Unique per request + c.logger.info('Request tracking', { + threadId: c.thread.id, // Same across conversation + sessionId: c.sessionId, // Unique per request requestNumber: sessionsSeen.size, - threadDuration: Date.now() - (ctx.thread.state.get('threadStartTime') as number || Date.now()) + threadDuration: Date.now() - (c.thread.state.get('threadStartTime') as number || Date.now()) }); // Initialize thread tracking - if (!ctx.thread.state.has('threadStartTime')) { - ctx.thread.state.set('threadStartTime', Date.now()); + if (!c.thread.state.has('threadStartTime')) { + c.thread.state.set('threadStartTime', Date.now()); } return { - threadId: ctx.thread.id, - sessionId: ctx.sessionId, + threadId: c.thread.id, + sessionId: c.sessionId, requestsInThread: sessionsSeen.size, explanation: { threadId: 'Groups related requests into a conversation (up to 1 hour)', diff --git a/content/v1/Guides/subagents.mdx b/content/v1/Guides/subagents.mdx index a0111e34..edb54516 100644 --- a/content/v1/Guides/subagents.mdx +++ b/content/v1/Guides/subagents.mdx @@ -3,7 +3,7 @@ title: Subagents description: Organize related agents into parent-child hierarchies --- -Subagents organize related agents into parent-child hierarchies with one level of nesting. Use subagents to group related functionality under a common parent, share validation logic, and create hierarchical API structures. Each subagent maintains its own handler and routes while accessing the parent agent via `ctx.parent`. +Subagents organize related agents into parent-child hierarchies with one level of nesting. Use subagents to group related functionality under a common parent, share validation logic, and create hierarchical API structures. Each subagent maintains its own handler and routes while accessing the parent agent via `c.parent`. For routing patterns and middleware configuration, see the [Routing & Triggers Guide](/Guides/routing-triggers). For general agent patterns, see [Core Concepts](/Introduction/core-concepts). @@ -53,10 +53,10 @@ Use separate agents when you have: | Feature | Subagents | Separate Agents | |---------|-----------|-----------------| | **File structure** | Nested directories | Flat structure | -| **Access pattern** | `ctx.agent.parent.child.run()` | `ctx.agent.agentName.run()` | +| **Access pattern** | `c.agent.parent.child.run()` | `c.agent.agentName.run()` | | **Routes** | Inherit parent path | Independent paths | | **Shared logic** | Parent provides context | Shared via middleware | -| **Parent access** | `ctx.parent` available | N/A | +| **Parent access** | `c.parent` available | N/A | | **Coupling** | Moderate (shares parent) | Low (independent) | | **Use case** | Related operations | Independent operations | @@ -80,15 +80,15 @@ const agent = createAgent({ taskCount: z.number().optional() }) }, - handler: async (ctx, input) => { + handler: async (c, input) => { if (input.action === 'info') { return { message: 'Team management system' }; } // Coordinate between subagents const [members, tasks] = await Promise.all([ - ctx.agent.team.members.run({ action: 'count' }), - ctx.agent.team.tasks.run({ action: 'count' }) + c.agent.team.members.run({ action: 'count' }), + c.agent.team.tasks.run({ action: 'count' }) ]); return { @@ -108,7 +108,7 @@ export default agent; **Key Points:** - Parent is a standard agent created with `createAgent()` -- Can call subagents via `ctx.agent.parent.child.run()` +- Can call subagents via `c.agent.parent.child.run()` - Provides high-level coordination between subagents - Routes follow standard patterns (see [Routing & Triggers Guide](/Guides/routing-triggers)) @@ -124,8 +124,8 @@ Subagents are created by placing agent.ts and route.ts files in a subdirectory o **Naming Convention:** - Agent name: `parent.child` (e.g., `team.members`) -- Property access: `ctx.agent.team.members` (nested object) -- Current agent name: `ctx.agentName` returns `"team.members"` +- Property access: `c.agent.team.members` (nested object) +- Current agent name: `c.agentName` returns `"team.members"` ### Basic Subagent @@ -147,16 +147,16 @@ const agent = createAgent({ count: z.number().optional() }) }, - handler: async (ctx, input) => { - const result = await ctx.kv.get('team-data', 'members'); + handler: async (c, input) => { + const result = await c.kv.get('team-data', 'members'); let members: string[] = result.exists && result.data ? result.data : []; if (input.action === 'add' && input.name) { members.push(input.name); - await ctx.kv.set('team-data', 'members', members); + await c.kv.set('team-data', 'members', members); } else if (input.action === 'remove' && input.name) { members = members.filter(m => m !== input.name); - await ctx.kv.set('team-data', 'members', members); + await c.kv.set('team-data', 'members', members); } else if (input.action === 'count') { return { members, count: members.length }; } @@ -171,7 +171,7 @@ export default agent; **Key Points:** - Same `createAgent()` syntax as regular agents - Agent name automatically becomes `team.members` -- Access via `ctx.agentName` returns `"team.members"` +- Access via `c.agentName` returns `"team.members"` - Full schema validation and type inference @@ -214,7 +214,7 @@ export default router; ## Accessing Subagents -Subagents are accessed via nested properties on `ctx.agent` or `c.agent`. Type inference provides autocomplete and compile-time validation when schemas are defined. +Subagents are accessed via nested properties on `c.agent` or `c.agent`. Type inference provides autocomplete and compile-time validation when schemas are defined. ```typescript // From routes or other agents @@ -235,15 +235,15 @@ router.get('/summary', async (c) => { // From another agent const agent = createAgent({ - handler: async (ctx, input) => { - const members = await ctx.agent.team.members.run({ action: 'count' }); + handler: async (c, input) => { + const members = await c.agent.team.members.run({ action: 'count' }); return { count: members.count }; } }); ``` **Key Points:** -- Nested property access: `ctx.agent.parent.child.run()` +- Nested property access: `c.agent.parent.child.run()` - Works from routes and other agents - Supports parallel execution with `Promise.all()` - Type-safe when both agents have schemas @@ -254,7 +254,7 @@ From within a subagent, access siblings via the full path (not via `ctx.sibling` ```typescript // From team/members/agent.ts -const tasksData = await ctx.agent.team.tasks.run({ action: 'list' }); // ✓ +const tasksData = await c.agent.team.tasks.run({ action: 'list' }); // ✓ ``` @@ -263,26 +263,26 @@ const tasksData = await ctx.agent.team.tasks.run({ action: 'list' }); // ✓ ## Parent Context Access -Subagents access their parent agent via `ctx.parent`. This enables validation, shared configuration, and coordination patterns while maintaining separation of concerns. +Subagents access their parent agent via `c.parent`. This enables validation, shared configuration, and coordination patterns while maintaining separation of concerns. ### Basic Usage -The `ctx.parent` property is available only in subagent handlers: +The `c.parent` property is available only in subagent handlers: ```typescript // team/members/agent.ts const agent = createAgent({ - handler: async (ctx, input) => { + handler: async (c, input) => { let parentInfo: string | undefined; // Check if parent is available - if (ctx.parent) { - const parentData = await ctx.parent.run({ action: 'info' }); + if (c.parent) { + const parentData = await c.parent.run({ action: 'info' }); parentInfo = parentData.message; - ctx.logger.info('Retrieved parent context', { + c.logger.info('Retrieved parent context', { parentMessage: parentInfo, - currentAgent: ctx.agentName // "team.members" + currentAgent: c.agentName // "team.members" }); } @@ -292,10 +292,10 @@ const agent = createAgent({ ``` **Key Points:** -- `ctx.parent` available only in subagents (undefined in parent) -- Call parent via `ctx.parent.run(input)` +- `c.parent` available only in subagents (undefined in parent) +- Call parent via `c.parent.run(input)` - Parent execution is synchronous (blocks subagent) -- Always check `if (ctx.parent)` for type safety +- Always check `if (c.parent)` for type safety ### Shared Validation Pattern @@ -315,8 +315,8 @@ const teamAgent = createAgent({ userRole: z.string() }) }, - handler: async (ctx, input) => { - const result = await ctx.kv.get('team-memberships', `${input.teamId}:${input.userId}`); + handler: async (c, input) => { + const result = await c.kv.get('team-memberships', `${input.teamId}:${input.userId}`); if (!result.exists) { throw new Error('User is not a member of this team'); @@ -341,10 +341,10 @@ const teamAgent = createAgent({ // team/members/agent.ts - Uses parent validation const membersAgent = createAgent({ - handler: async (ctx, input) => { + handler: async (c, input) => { // Validate with admin role requirement - if (ctx.parent) { - await ctx.parent.run({ + if (c.parent) { + await c.parent.run({ userId: input.userId, teamId: input.teamId, requiredRole: 'admin' @@ -352,7 +352,7 @@ const membersAgent = createAgent({ } // Admin-only operation - const members = await ctx.kv.get('team-members', input.teamId); + const members = await c.kv.get('team-members', input.teamId); return { members: members.data || [] }; } }); @@ -366,12 +366,12 @@ const membersAgent = createAgent({ ### When to Use Parent Access -Use `ctx.parent` for: +Use `c.parent` for: - **Validation** - Check permissions before executing subagent logic - **Shared configuration** - Retrieve settings managed by parent - **Logging context** - Include parent information in logs -Avoid `ctx.parent` for: +Avoid `c.parent` for: - **Core business logic** - Subagent should be mostly independent - **Data operations** - Use storage directly, not via parent - **Frequent calls** - Each call is synchronous and blocks execution @@ -462,8 +462,8 @@ const userAgent = createAgent({ valid: z.boolean().optional() }) }, - handler: async (ctx, input) => { - const result = await ctx.kv.get('users', input.userId); + handler: async (c, input) => { + const result = await c.kv.get('users', input.userId); if (!result.exists) throw new Error('User not found'); const user = result.data; @@ -499,10 +499,10 @@ const profileAgent = createAgent({ }) }) }, - handler: async (ctx, input) => { + handler: async (c, input) => { // Validate user via parent - if (ctx.parent) { - const validation = await ctx.parent.run({ + if (c.parent) { + const validation = await c.parent.run({ userId: input.userId, action: 'validate' }); @@ -511,7 +511,7 @@ const profileAgent = createAgent({ } } - const result = await ctx.kv.get(`profile-${input.userId}`, 'user-profiles'); + const result = await c.kv.get(`profile-${input.userId}`, 'user-profiles'); let profile = result.exists ? result.data : { displayName: 'New User', bio: '' @@ -519,7 +519,7 @@ const profileAgent = createAgent({ if (input.action === 'update' && input.profile) { profile = { ...profile, ...input.profile }; - await ctx.kv.set('user-profiles', `profile-${input.userId}`, profile); + await c.kv.set('user-profiles', `profile-${input.userId}`, profile); } return { profile }; @@ -567,10 +567,10 @@ For complete working examples of these patterns, see the [Examples](/Examples) p ```typescript // Good: Parent coordinates const teamAgent = createAgent({ - handler: async (ctx, input) => { + handler: async (c, input) => { const [members, tasks] = await Promise.all([ - ctx.agent.team.members.run({ action: 'count' }), - ctx.agent.team.tasks.run({ action: 'count' }) + c.agent.team.members.run({ action: 'count' }), + c.agent.team.tasks.run({ action: 'count' }) ]); return { memberCount: members.count, taskCount: tasks.count }; } @@ -578,8 +578,8 @@ const teamAgent = createAgent({ // Bad: Parent implements domain logic const teamAgent = createAgent({ - handler: async (ctx, input) => { - const members = await ctx.kv.get('members', 'list'); + handler: async (c, input) => { + const members = await c.kv.get('members', 'list'); return { members: members.data.length }; // Should delegate to subagent } }); @@ -608,8 +608,8 @@ team/memberAdmins/agent.ts // Separate subagent **Cache parent context if calling multiple times:** ```typescript const membersAgent = createAgent({ - handler: async (ctx, input) => { - const parentData = ctx.parent ? await ctx.parent.run({ teamId: input.teamId }) : null; + handler: async (c, input) => { + const parentData = c.parent ? await c.parent.run({ teamId: input.teamId }) : null; // Use parentData multiple times without re-calling return { processed: true }; } @@ -620,13 +620,13 @@ const membersAgent = createAgent({ ```typescript // Good const [members, tasks] = await Promise.all([ - ctx.agent.team.members.run({ action: 'count' }), - ctx.agent.team.tasks.run({ action: 'count' }) + c.agent.team.members.run({ action: 'count' }), + c.agent.team.tasks.run({ action: 'count' }) ]); // Bad - slower when independent -const members = await ctx.agent.team.members.run({ action: 'count' }); -const tasks = await ctx.agent.team.tasks.run({ action: 'count' }); +const members = await c.agent.team.members.run({ action: 'count' }); +const tasks = await c.agent.team.tasks.run({ action: 'count' }); ``` ### Testing @@ -668,9 +668,9 @@ describe('Members Subagent', () => { **Let validation errors propagate:** ```typescript const membersAgent = createAgent({ - handler: async (ctx, input) => { - if (ctx.parent) { - await ctx.parent.run({ userId: input.userId, teamId: input.teamId }); + handler: async (c, input) => { + if (c.parent) { + await c.parent.run({ userId: input.userId, teamId: input.teamId }); // Parent throws on validation failure - error propagates to caller } return { members: [] }; @@ -681,12 +681,12 @@ const membersAgent = createAgent({ **Graceful degradation for optional parent calls:** ```typescript const membersAgent = createAgent({ - handler: async (ctx, input) => { - if (ctx.parent) { + handler: async (c, input) => { + if (c.parent) { try { - await ctx.parent.run({ userId: input.userId }); + await c.parent.run({ userId: input.userId }); } catch (error) { - ctx.logger.warn('Parent validation unavailable', { error }); + c.logger.warn('Parent validation unavailable', { error }); // Continue with degraded functionality } } @@ -733,15 +733,15 @@ export function TeamDashboard() { ## Next Steps **Explore Related Guides:** -- [Routing & Triggers](/Guides/routing-triggers) - HTTP methods, WebSocket, SSE, and specialized routes -- [Schema Validation](/Guides/schema-validation) - Type-safe schemas with Zod/Valibot/ArkType -- [Events](/Guides/events) - Monitor agent execution with lifecycle hooks -- [Sessions & Threads](/Guides/sessions-threads) - Stateful context management +- [Routing](/Guides/routing): Complete routing patterns and middleware +- [Schema Validation](/Guides/schema-validation): Type-safe schemas with Zod/Valibot/ArkType +- [Events](/Guides/events): Monitor agent execution with lifecycle hooks +- [Sessions & Threads](/Guides/sessions-threads): Stateful context management **API Reference:** -- [AgentContext](/api-reference#agentcontext) - Complete context API including `ctx.parent` -- [createAgent](/api-reference#createagent) - Agent creation and configuration -- [createRouter](/api-reference#createrouter) - Route definition and middleware +- [AgentContext](/api-reference#agentcontext): Complete context API including `c.parent` +- [createAgent](/api-reference#createagent): Agent creation and configuration +- [createRouter](/api-reference#createrouter): Route definition and middleware **Examples:** -- [Examples](/Examples) - Additional patterns and complete working examples +- [Examples](/Examples): Additional patterns and complete working examples diff --git a/content/v1/Guides/vector-storage.mdx b/content/v1/Guides/vector-storage.mdx index 3f96aa80..77908db8 100644 --- a/content/v1/Guides/vector-storage.mdx +++ b/content/v1/Guides/vector-storage.mdx @@ -948,7 +948,7 @@ TODO: Add video for v1 storage overview ## Next Steps -- [Evaluations](/Guides/evaluations) - Add quality checks for RAG systems (hallucination detection, faithfulness, relevancy) -- [Key-Value Storage](/Guides/key-value-storage) - Fast lookups for simple data -- [Sessions and Threads](/Guides/sessions-threads) - Manage conversation state -- [API Reference](/SDK/api-reference) - Complete vector storage API documentation +- [Evaluations](/Guides/evaluations): Add quality checks for RAG systems (hallucination detection, faithfulness, relevancy) +- [Key-Value Storage](/Guides/key-value-storage): Fast lookups for simple data +- [Sessions and Threads](/Guides/sessions-threads): Manage conversation state +- [API Reference](/SDK/api-reference): Complete vector storage API documentation diff --git a/content/v1/Introduction/architecture.mdx b/content/v1/Introduction/architecture.mdx index a5ac2d82..f8ced639 100644 --- a/content/v1/Introduction/architecture.mdx +++ b/content/v1/Introduction/architecture.mdx @@ -222,8 +222,8 @@ export default createAgent({ name: 'My Agent', description: 'A simple agent' }, - handler: async (ctx, input) => { - ctx.logger.info('Processing request', { input }); + handler: async (c, input) => { + c.logger.info('Processing request', { input }); return { response: `Received: ${input.message}` @@ -374,7 +374,7 @@ agent.createEval({ name: 'quality-check', description: 'Validates output quality' }, - handler: async (ctx, input, output) => { + handler: async (c, input, output) => { // Evaluate the output const score = calculateQuality(output); @@ -404,7 +404,7 @@ const agent = createAgent({ greeting: z.string() }) }, - handler: async (ctx, input) => { + handler: async (c, input) => { // input is fully typed and validated return { greeting: `Hello ${input.name}!` @@ -417,10 +417,10 @@ const agent = createAgent({ These conventions enable several key capabilities: -1. **Consistent Development Experience** - Standardized structure makes it easier to work across projects -2. **Automated Deployment** - The CLI can package and deploy your project without additional configuration -3. **Framework Flexibility** - Use any agent framework while maintaining compatibility with the platform -4. **Type Safety** - Full TypeScript support throughout the stack -5. **Scalability** - Clear separation of concerns makes it easy to organize complex agent systems +1. **Consistent Development Experience**: Standardized structure makes it easier to work across projects +2. **Automated Deployment**: The CLI can package and deploy your project without additional configuration +3. **Framework Flexibility**: Use any agent framework while maintaining compatibility with the platform +4. **Type Safety**: Full TypeScript support throughout the stack +5. **Scalability**: Clear separation of concerns makes it easy to organize complex agent systems TODO: Add examples showing integration with frontend frameworks (Next.js, Svelte, vanilla JS, etc.) for building UIs that call agents diff --git a/content/v1/Introduction/core-concepts.mdx b/content/v1/Introduction/core-concepts.mdx index b08989fc..5c37b939 100644 --- a/content/v1/Introduction/core-concepts.mdx +++ b/content/v1/Introduction/core-concepts.mdx @@ -17,8 +17,8 @@ const agent = createAgent({ name: 'My Agent', description: 'A simple agent that greets users' }, - handler: async (ctx) => { - ctx.logger.info('Agent invoked'); + handler: async (c) => { + c.logger.info('Agent invoked'); return { message: 'Hello!' }; } }); @@ -56,7 +56,7 @@ const agent = createAgent({ greeting: z.string() }) }, - handler: async (ctx, input) => { + handler: async (c, input) => { // input is fully typed and validated! return { greeting: `Hello ${input.name}!` @@ -152,7 +152,7 @@ const agent = createAgent({ schema: { input: z.object({ message: z.string() }) }, - handler: async (ctx, input) => { + handler: async (c, input) => { // input is already parsed and validated return { echo: input.message }; } @@ -180,7 +180,7 @@ By default, returning an object will automatically serialize it as JSON. For mor **Simple Returns:** ```typescript -handler: async (ctx, input) => { +handler: async (c, input) => { return { message: 'Done!' }; // Automatically becomes JSON } ``` @@ -211,9 +211,9 @@ To call another agent, use `ctx.agent.otherAgent.run()`. This provides direct ac ```typescript const agent = createAgent({ - handler: async (ctx, input) => { + handler: async (c, input) => { // Call another agent - const result = await ctx.agent.helperAgent.run({ + const result = await c.agent.helperAgent.run({ data: input.data }); @@ -237,16 +237,16 @@ Sessions track individual executions, while threads track conversation histories **Threads** represent conversation histories for multi-turn interactions. Use threads to maintain context across multiple requests. ```typescript -handler: async (ctx, input) => { +handler: async (c, input) => { // Log session info - ctx.logger.info('Session:', { - id: ctx.sessionId, - session: ctx.session + c.logger.info('Session:', { + id: c.sessionId, + session: c.session }); // Track conversation turns - const turnCount = (ctx.state.get('turnCount') as number || 0) + 1; - ctx.state.set('turnCount', turnCount); + const turnCount = (c.state.get('turnCount') as number || 0) + 1; + c.state.set('turnCount', turnCount); return { turn: turnCount, @@ -285,7 +285,7 @@ agent.createEval({ name: 'quality-check', description: 'Validates output quality' }, - handler: async (ctx, input, output) => { + handler: async (c, input, output) => { const score = calculateQuality(output); return { success: true, diff --git a/content/v1/Introduction/introduction.mdx b/content/v1/Introduction/introduction.mdx index 190f0cb3..d7e42397 100644 --- a/content/v1/Introduction/introduction.mdx +++ b/content/v1/Introduction/introduction.mdx @@ -35,4 +35,4 @@ We see a near future where Agents are the primary way to build and operate softw TODO: Add platform overview video -If you're ready to have your own Agent, [Get Started](/Introduction/getting-started) in just a few minutes. \ No newline at end of file +If you're ready to have your own Agent, [Get Started](/Introduction/getting-started) in just a few minutes. diff --git a/content/v1/SDK/core-concepts.mdx b/content/v1/SDK/core-concepts.mdx index e3ffa2ab..130e21a6 100644 --- a/content/v1/SDK/core-concepts.mdx +++ b/content/v1/SDK/core-concepts.mdx @@ -26,7 +26,7 @@ const agent = createAgent({ name: 'Chat Agent', description: 'Processes chat messages' }, - handler: async (ctx, input) => { + handler: async (c, input) => { // Input is already validated and typed return { response: `You said: ${input.message}` }; } @@ -57,7 +57,7 @@ const agent = createAgent({ verified: z.boolean() }) }, - handler: async (ctx, input) => { + handler: async (c, input) => { // input.email and input.age are fully typed // Return type is also validated return { @@ -83,7 +83,7 @@ import { createAgent } from '@agentuity/runtime'; export const chatAgent = createAgent({ schema: { /* ... */ }, - handler: async (ctx, input) => { /* ... */ } + handler: async (c, input) => { /* ... */ } }); ``` @@ -115,9 +115,9 @@ Agent handlers receive two parameters: Handlers return data directly. The SDK handles serialization and response formatting. ```typescript -handler: async (ctx, input) => { +handler: async (c, input) => { // Access context capabilities - ctx.logger.info('Processing request', { input }); + c.logger.info('Processing request', { input }); // Return data directly (no response object needed) return { success: true, data: processedData }; @@ -140,7 +140,7 @@ const agent = createAgent({ filters: z.array(z.string()).optional() }) }, - handler: async (ctx, input) => { + handler: async (c, input) => { // input is validated and typed // TypeScript knows: input.query is string // TypeScript knows: input.filters is string[] | undefined @@ -163,7 +163,7 @@ const agent = createAgent({ data: z.any() }) }, - handler: async (ctx, input) => { + handler: async (c, input) => { // Return must match output schema return { status: 'success', @@ -186,7 +186,7 @@ const agent = createAgent({ input: z.object({ prompt: z.string() }), stream: true }, - handler: async (ctx, input) => { + handler: async (c, input) => { const { textStream } = streamText({ model: openai('gpt-5-mini'), prompt: input.prompt @@ -208,12 +208,12 @@ The context object provides access to all SDK capabilities within your handler. Access unique identifiers for the current execution: ```typescript -handler: async (ctx, input) => { +handler: async (c, input) => { // Unique ID for this execution - const sessionId = ctx.sessionId; + const sessionId = c.sessionId; // Name of the current agent - const agentName = ctx.agentName; + const agentName = c.agentName; } ``` @@ -222,17 +222,17 @@ handler: async (ctx, input) => { Call other agents in your project or access agent references: ```typescript -handler: async (ctx, input) => { +handler: async (c, input) => { // Call another agent (type-safe) - const result = await ctx.agent.enrichmentAgent.run({ + const result = await c.agent.enrichmentAgent.run({ text: input.data }); // Reference to current agent - const self = ctx.current; + const self = c.current; // Parent agent reference (in subagents) - const parent = ctx.parent; + const parent = c.parent; if (parent) { const parentResult = await parent.run(input); } @@ -246,25 +246,25 @@ Agent calls are type-safe when both agents have schemas. See [Agent Communicatio Access state at three different scopes: ```typescript -handler: async (ctx, input) => { +handler: async (c, input) => { // Request scope - cleared after response - ctx.state.set('startTime', Date.now()); + c.state.set('startTime', Date.now()); // Thread scope - conversation context (1 hour lifetime) - const history = ctx.thread.state.get('messages') || []; - ctx.thread.state.set('messages', [...history, input.message]); + const history = c.thread.state.get('messages') || []; + c.thread.state.set('messages', [...history, input.message]); // Session scope - spans threads - const userPrefs = ctx.session.state.get('preferences'); - ctx.session.state.set('lastActive', new Date()); + const userPrefs = c.session.state.get('preferences'); + c.session.state.set('lastActive', new Date()); } ``` | Scope | Lifetime | Use Case | |-------|----------|----------| -| `ctx.state` | Single request | Timing, temporary calculations | -| `ctx.thread.state` | Up to 1 hour | Conversation history | -| `ctx.session.state` | Spans threads | User preferences, settings | +| `c.state` | Single request | Timing, temporary calculations | +| `c.thread.state` | Up to 1 hour | Conversation history | +| `c.session.state` | Spans threads | User preferences, settings | See the [Sessions and Threads](/Guides/sessions-threads) guide for detailed state management patterns. @@ -273,25 +273,25 @@ See the [Sessions and Threads](/Guides/sessions-threads) guide for detailed stat Access persistent storage systems: ```typescript -handler: async (ctx, input) => { +handler: async (c, input) => { // Key-value storage - await ctx.kv.set('cache', 'user-123', userData, { ttl: 3600 }); - const cached = await ctx.kv.get('cache', 'user-123'); + await c.kv.set('cache', 'user-123', userData, { ttl: 3600 }); + const cached = await c.kv.get('cache', 'user-123'); // Vector storage - const results = await ctx.vector.search('products', { + const results = await c.vector.search('products', { query: input.searchTerm, limit: 5, similarity: 0.7 }); // Object storage - await ctx.objectstore.put('uploads', 'file.pdf', fileData, { + await c.objectstore.put('uploads', 'file.pdf', fileData, { contentType: 'application/pdf' }); // Stream storage - const stream = await ctx.stream.create('export', { + const stream = await c.stream.create('export', { contentType: 'text/csv' }); } @@ -310,13 +310,13 @@ See the [API Reference](/SDK/api-reference) for complete storage API documentati Monitor and trace agent execution: ```typescript -handler: async (ctx, input) => { +handler: async (c, input) => { // Structured logging - ctx.logger.info('Processing request', { input }); - ctx.logger.error('Operation failed', { error: err }); + c.logger.info('Processing request', { input }); + c.logger.error('Operation failed', { error: err }); // OpenTelemetry tracing - const span = ctx.tracer.startSpan('database-query'); + const span = c.tracer.startSpan('database-query'); // ... perform operation span.end(); } @@ -327,14 +327,14 @@ handler: async (ctx, input) => { Execute background tasks that continue after the response is sent: ```typescript -handler: async (ctx, input) => { +handler: async (c, input) => { // Process data immediately const result = processData(input); // Run analytics in background - ctx.waitUntil(async () => { + c.waitUntil(async () => { await trackAnalytics(result); - await updateMetrics(ctx.sessionId); + await updateMetrics(c.sessionId); }); // Response sent immediately, background tasks continue @@ -438,13 +438,13 @@ Agents can call other agents in the same project using type-safe references thro ### Calling Other Agents -Use `ctx.agent.agentName.run()` to invoke another agent: +Use `c.agent.agentName.run()` to invoke another agent: ```typescript const coordinatorAgent = createAgent({ - handler: async (ctx, input) => { + handler: async (c, input) => { // Call another agent - const enriched = await ctx.agent.enrichmentAgent.run({ + const enriched = await c.agent.enrichmentAgent.run({ text: input.rawData }); @@ -461,10 +461,10 @@ When both agents have schemas, the call is fully type-safe. TypeScript will vali Execute agents sequentially when each depends on the previous result: ```typescript -handler: async (ctx, input) => { - const step1 = await ctx.agent.validator.run(input); - const step2 = await ctx.agent.enricher.run(step1); - const step3 = await ctx.agent.analyzer.run(step2); +handler: async (c, input) => { + const step1 = await c.agent.validator.run(input); + const step2 = await c.agent.enricher.run(step1); + const step3 = await c.agent.analyzer.run(step2); return step3; } @@ -473,11 +473,11 @@ handler: async (ctx, input) => { Execute agents in parallel when operations are independent: ```typescript -handler: async (ctx, input) => { +handler: async (c, input) => { const [webResults, dbResults, cacheResults] = await Promise.all([ - ctx.agent.webSearch.run(input), - ctx.agent.database.run(input), - ctx.agent.cache.run(input) + c.agent.webSearch.run(input), + c.agent.database.run(input), + c.agent.cache.run(input) ]); return { webResults, dbResults, cacheResults }; @@ -490,12 +490,12 @@ Access subagents using the nested path syntax: ```typescript // Call a subagent from anywhere -const result = await ctx.agent.team.members.run({ +const result = await c.agent.team.members.run({ action: 'list' }); // From a subagent, access the parent -const parentResult = await ctx.parent.run(input); +const parentResult = await c.parent.run(input); ``` Subagents organize related agents into parent-child hierarchies with one level of nesting. For detailed patterns and limitations, see the [Subagents](/Guides/subagents) guide. @@ -506,9 +506,9 @@ Agent calls execute within the same session context, sharing the same `sessionId ## Next Steps -- [Schema Validation](/Guides/schema-validation) - Advanced schema patterns and validation -- [Routing & Triggers](/Guides/routing-triggers) - HTTP methods, WebSocket, SSE, and specialized routes -- [Sessions and Threads](/Guides/sessions-threads) - State management patterns -- [Evaluations](/Guides/evaluations) - Automated quality testing -- [Events](/Guides/events) - Lifecycle hooks and monitoring -- [API Reference](/SDK/api-reference) - Complete API documentation +- [Schema Validation](/Guides/schema-validation): Advanced schema patterns and validation +- [Routing & Triggers](/Guides/routing-triggers): HTTP methods, WebSocket, SSE, and specialized routes +- [Sessions and Threads](/Guides/sessions-threads): State management patterns +- [Evaluations](/Guides/evaluations): Automated quality testing +- [Events](/Guides/events): Lifecycle hooks and monitoring +- [API Reference](/SDK/api-reference): Complete API documentation diff --git a/content/v1/SDK/error-handling.mdx b/content/v1/SDK/error-handling.mdx index 98d79bef..e7b75366 100644 --- a/content/v1/SDK/error-handling.mdx +++ b/content/v1/SDK/error-handling.mdx @@ -35,14 +35,14 @@ const agent = createAgent({ data: z.any() }) }, - handler: async (ctx, input) => { + handler: async (c, input) => { // Throw errors when business logic fails if (input.operation === 'create' && !input.data) { throw new Error('Data is required for create operation'); } // Throw errors for authorization failures - const user = await getUserById(ctx.sessionId); + const user = await getUserById(c.sessionId); if (!user.canPerform(input.operation)) { throw new Error('Unauthorized to perform this operation'); } @@ -60,12 +60,12 @@ When agents call other agents, errors propagate automatically: ```typescript const coordinatorAgent = createAgent({ - handler: async (ctx, input) => { + handler: async (c, input) => { // If validatorAgent throws an error, it propagates here - const validation = await ctx.agent.validatorAgent.run(input); + const validation = await c.agent.validatorAgent.run(input); // If enrichmentAgent throws an error, it propagates here - const enriched = await ctx.agent.enrichmentAgent.run(validation); + const enriched = await c.agent.enrichmentAgent.run(validation); return enriched; } @@ -80,14 +80,14 @@ For optional operations, use try-catch to handle errors gracefully: ```typescript const agent = createAgent({ - handler: async (ctx, input) => { + handler: async (c, input) => { let enrichedData = input.data; // Try optional enrichment, but continue if it fails try { - enrichedData = await ctx.agent.enrichmentAgent.run(input); + enrichedData = await c.agent.enrichmentAgent.run(input); } catch (error) { - ctx.logger.warn('Enrichment failed, using original data', { + c.logger.warn('Enrichment failed, using original data', { error: error instanceof Error ? error.message : String(error) }); // Continue with original data @@ -125,7 +125,7 @@ class AuthorizationError extends Error { } const agent = createAgent({ - handler: async (ctx, input) => { + handler: async (c, input) => { const user = await getUser(input.userId); if (!user) { @@ -259,7 +259,7 @@ const agent = createAgent({ role: z.enum(['user', 'admin']) }) }, - handler: async (ctx, input) => { + handler: async (c, input) => { // If we reach this point, input is guaranteed to be valid // - input.email is a valid email address // - input.age is >= 18 @@ -284,7 +284,7 @@ const agent = createAgent({ data: z.any().optional() }) }, - handler: async (ctx, input) => { + handler: async (c, input) => { // This return value is validated return { status: 'success', @@ -307,15 +307,15 @@ Storage operations can fail due to network issues, timeouts, or service unavaila ```typescript const agent = createAgent({ - handler: async (ctx, input) => { + handler: async (c, input) => { try { - const cached = await ctx.kv.get('cache', input.key); + const cached = await c.kv.get('cache', input.key); if (cached.exists) { return { source: 'cache', data: cached.data }; } } catch (error) { - ctx.logger.warn('Cache lookup failed, falling back to database', { + c.logger.warn('Cache lookup failed, falling back to database', { error: error instanceof Error ? error.message : String(error) }); } @@ -325,9 +325,9 @@ const agent = createAgent({ // Try to cache for next time (don't fail if this errors) try { - await ctx.kv.set('cache', input.key, data, { ttl: 3600 }); + await c.kv.set('cache', input.key, data, { ttl: 3600 }); } catch (error) { - ctx.logger.warn('Failed to cache result', { error }); + c.logger.warn('Failed to cache result', { error }); } return { source: 'database', data }; @@ -339,9 +339,9 @@ const agent = createAgent({ ```typescript const agent = createAgent({ - handler: async (ctx, input) => { + handler: async (c, input) => { try { - const results = await ctx.vector.search('products', { + const results = await c.vector.search('products', { query: input.searchTerm, limit: 5, similarity: 0.7 @@ -349,7 +349,7 @@ const agent = createAgent({ return { results }; } catch (error) { - ctx.logger.error('Vector search failed', { + c.logger.error('Vector search failed', { error: error instanceof Error ? error.message : String(error) }); @@ -365,13 +365,13 @@ const agent = createAgent({ ```typescript const agent = createAgent({ - handler: async (ctx, input) => { + handler: async (c, input) => { try { - await ctx.objectstore.put('uploads', input.filename, input.data, { + await c.objectstore.put('uploads', input.filename, input.data, { contentType: input.contentType }); - const url = await ctx.objectstore.createPublicURL( + const url = await c.objectstore.createPublicURL( 'uploads', input.filename, 3600000 // 1 hour @@ -379,7 +379,7 @@ const agent = createAgent({ return { success: true, url }; } catch (error) { - ctx.logger.error('Failed to upload file', { + c.logger.error('Failed to upload file', { filename: input.filename, error: error instanceof Error ? error.message : String(error) }); @@ -406,7 +406,7 @@ const agent = createAgent({ name: 'Processing Agent', description: 'Processes user data' }, - handler: async (ctx, input) => { + handler: async (c, input) => { // Handler logic return { success: true }; } @@ -415,9 +415,9 @@ const agent = createAgent({ // Listen for errors agent.addEventListener('errored', (event, agent, ctx, error) => { // Log error with context - ctx.logger.error('Agent execution failed', { + c.logger.error('Agent execution failed', { agentName: agent.metadata.name, - sessionId: ctx.sessionId, + sessionId: c.sessionId, error: error.message, stack: error.stack }); @@ -425,7 +425,7 @@ agent.addEventListener('errored', (event, agent, ctx, error) => { // Send to error tracking service trackError({ agent: agent.metadata.name, - session: ctx.sessionId, + session: c.sessionId, error: error.message }); }); @@ -446,19 +446,19 @@ Use the logger to track execution flow and identify issues: ```typescript const agent = createAgent({ - handler: async (ctx, input) => { - ctx.logger.info('Processing started', { input }); + handler: async (c, input) => { + c.logger.info('Processing started', { input }); try { const step1 = await performStep1(input); - ctx.logger.debug('Step 1 completed', { step1 }); + c.logger.debug('Step 1 completed', { step1 }); const step2 = await performStep2(step1); - ctx.logger.debug('Step 2 completed', { step2 }); + c.logger.debug('Step 2 completed', { step2 }); return { result: step2 }; } catch (error) { - ctx.logger.error('Processing failed', { + c.logger.error('Processing failed', { error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined }); @@ -481,8 +481,8 @@ Use tracing to debug complex workflows: ```typescript const agent = createAgent({ - handler: async (ctx, input) => { - const span = ctx.tracer.startSpan('process-data'); + handler: async (c, input) => { + const span = c.tracer.startSpan('process-data'); try { span.setAttribute('input.size', JSON.stringify(input).length); @@ -574,7 +574,7 @@ throw new Error('Unable to authenticate user'); try { await authenticateUser(token); } catch (error) { - ctx.logger.error('Authentication failed', { + c.logger.error('Authentication failed', { error: error.message, token: token.substring(0, 8) + '...' // Partially redacted }); @@ -620,7 +620,7 @@ Testing error scenarios ensures your agents handle failures gracefully. ## Next Steps -- [Schema Validation](/Guides/schema-validation) - Define schemas to validate input and output automatically -- [Events](/Guides/events) - Monitor agent lifecycle and handle errors with event listeners -- [Routing & Triggers](/Guides/routing-triggers) - Create routes that handle HTTP requests and responses -- [API Reference](/SDK/api-reference) - Complete API documentation for context, storage, and utilities +- [Schema Validation](/Guides/schema-validation): Define schemas to validate input and output automatically +- [Events](/Guides/events): Monitor agent lifecycle and handle errors with event listeners +- [Routing & Triggers](/Guides/routing-triggers): Create routes that handle HTTP requests and responses +- [API Reference](/SDK/api-reference): Complete API documentation for context, storage, and utilities From 28e1761a4f56be2c789e47826c0a72395731d657 Mon Sep 17 00:00:00 2001 From: parteeksingh24 Date: Wed, 19 Nov 2025 09:49:10 -0800 Subject: [PATCH 03/63] Add (initial) training modules and examples --- .../v1/Training/01-intro-to-agents/index.mdx | 726 ++++++++++++++++++ .../Training/02-anatomy-of-an-agent/index.mdx | 347 +++++++++ .../03-specialized-routes-triggers/index.mdx | 243 ++++++ .../v1/Training/04-state-management/index.mdx | 291 +++++++ content/v1/Training/05-storage-apis/index.mdx | 381 +++++++++ .../01-intro/step1-basic-agent/agent.ts | 30 + .../01-intro/step1-basic-agent/route.ts | 15 + .../01-intro/step2-with-logging/agent.ts | 24 + .../01-intro/step2-with-logging/route.ts | 9 + .../01-intro/step3-with-storage/agent.ts | 46 ++ .../01-intro/step3-with-storage/route.ts | 9 + .../01-intro/step4-schema-validation/agent.ts | 48 ++ .../01-intro/step4-schema-validation/route.ts | 9 + .../step1-two-file-pattern/agent.ts | 35 + .../step1-two-file-pattern/route.ts | 13 + .../02-anatomy/step2-http-routing/agent.ts | 76 ++ .../02-anatomy/step2-http-routing/route.ts | 57 ++ .../02-anatomy/step3-context-object/agent.ts | 38 + .../02-anatomy/step3-context-object/route.ts | 38 + .../step4-request-response/agent.ts | 47 ++ .../step4-request-response/route.ts | 143 ++++ .../step1-status-checker/agent.ts | 36 + .../step1-status-checker/route.ts | 31 + .../step2-email-responder/agent.ts | 30 + .../step2-email-responder/route.ts | 37 + .../step3-realtime-echo/agent.ts | 24 + .../step3-realtime-echo/route.ts | 54 ++ .../agent.ts | 36 + .../route.ts | 48 ++ .../step1-request-state/agent.ts | 51 ++ .../step1-request-state/route.ts | 28 + .../step2-thread-state/agent.ts | 56 ++ .../step2-thread-state/route.ts | 39 + .../step3-session-state/agent.ts | 74 ++ .../step3-session-state/route.ts | 42 + .../step4-state-lifecycle/agent.ts | 67 ++ .../step4-state-lifecycle/route.ts | 41 + .../05-storage-apis/step1-kv-basics/agent.ts | 68 ++ .../05-storage-apis/step1-kv-basics/route.ts | 28 + .../step2-kv-persistent/agent.ts | 84 ++ .../step2-kv-persistent/route.ts | 29 + .../step3-vector-basics/agent.ts | 94 +++ .../step3-vector-basics/route.ts | 16 + .../step4-vector-filtering/agent.ts | 115 +++ .../step4-vector-filtering/route.ts | 18 + .../step5-object-storage/agent.ts | 77 ++ .../step5-object-storage/route.ts | 17 + 47 files changed, 3865 insertions(+) create mode 100644 content/v1/Training/01-intro-to-agents/index.mdx create mode 100644 content/v1/Training/02-anatomy-of-an-agent/index.mdx create mode 100644 content/v1/Training/03-specialized-routes-triggers/index.mdx create mode 100644 content/v1/Training/04-state-management/index.mdx create mode 100644 content/v1/Training/05-storage-apis/index.mdx create mode 100644 examples/training/01-intro/step1-basic-agent/agent.ts create mode 100644 examples/training/01-intro/step1-basic-agent/route.ts create mode 100644 examples/training/01-intro/step2-with-logging/agent.ts create mode 100644 examples/training/01-intro/step2-with-logging/route.ts create mode 100644 examples/training/01-intro/step3-with-storage/agent.ts create mode 100644 examples/training/01-intro/step3-with-storage/route.ts create mode 100644 examples/training/01-intro/step4-schema-validation/agent.ts create mode 100644 examples/training/01-intro/step4-schema-validation/route.ts create mode 100644 examples/training/02-anatomy/step1-two-file-pattern/agent.ts create mode 100644 examples/training/02-anatomy/step1-two-file-pattern/route.ts create mode 100644 examples/training/02-anatomy/step2-http-routing/agent.ts create mode 100644 examples/training/02-anatomy/step2-http-routing/route.ts create mode 100644 examples/training/02-anatomy/step3-context-object/agent.ts create mode 100644 examples/training/02-anatomy/step3-context-object/route.ts create mode 100644 examples/training/02-anatomy/step4-request-response/agent.ts create mode 100644 examples/training/02-anatomy/step4-request-response/route.ts create mode 100644 examples/training/03-specialized-routes/step1-status-checker/agent.ts create mode 100644 examples/training/03-specialized-routes/step1-status-checker/route.ts create mode 100644 examples/training/03-specialized-routes/step2-email-responder/agent.ts create mode 100644 examples/training/03-specialized-routes/step2-email-responder/route.ts create mode 100644 examples/training/03-specialized-routes/step3-realtime-echo/agent.ts create mode 100644 examples/training/03-specialized-routes/step3-realtime-echo/route.ts create mode 100644 examples/training/03-specialized-routes/step4-multi-trigger-notifications/agent.ts create mode 100644 examples/training/03-specialized-routes/step4-multi-trigger-notifications/route.ts create mode 100644 examples/training/04-state-management/step1-request-state/agent.ts create mode 100644 examples/training/04-state-management/step1-request-state/route.ts create mode 100644 examples/training/04-state-management/step2-thread-state/agent.ts create mode 100644 examples/training/04-state-management/step2-thread-state/route.ts create mode 100644 examples/training/04-state-management/step3-session-state/agent.ts create mode 100644 examples/training/04-state-management/step3-session-state/route.ts create mode 100644 examples/training/04-state-management/step4-state-lifecycle/agent.ts create mode 100644 examples/training/04-state-management/step4-state-lifecycle/route.ts create mode 100644 examples/training/05-storage-apis/step1-kv-basics/agent.ts create mode 100644 examples/training/05-storage-apis/step1-kv-basics/route.ts create mode 100644 examples/training/05-storage-apis/step2-kv-persistent/agent.ts create mode 100644 examples/training/05-storage-apis/step2-kv-persistent/route.ts create mode 100644 examples/training/05-storage-apis/step3-vector-basics/agent.ts create mode 100644 examples/training/05-storage-apis/step3-vector-basics/route.ts create mode 100644 examples/training/05-storage-apis/step4-vector-filtering/agent.ts create mode 100644 examples/training/05-storage-apis/step4-vector-filtering/route.ts create mode 100644 examples/training/05-storage-apis/step5-object-storage/agent.ts create mode 100644 examples/training/05-storage-apis/step5-object-storage/route.ts diff --git a/content/v1/Training/01-intro-to-agents/index.mdx b/content/v1/Training/01-intro-to-agents/index.mdx new file mode 100644 index 00000000..e35644c3 --- /dev/null +++ b/content/v1/Training/01-intro-to-agents/index.mdx @@ -0,0 +1,726 @@ +--- +title: "Module 1: Introduction to Agents" +description: Understanding AI agents and the $47B opportunity +--- + +Welcome to the age of AI agents - autonomous systems that are fundamentally transforming how we build and think about software. + +## The $47B Agent Opportunity + +The AI agents market is exploding - projected to grow from [$5.1B in 2024 to $47.1B by 2030](https://www.marketsandmarkets.com/Market-Reports/ai-agents-market-15761548.html) at a staggering 44.8% CAGR. According to [IBM's latest research](https://www.ibm.com/think/insights/ai-agents-2025-expectations-vs-reality), many developers are already exploring or building AI agents. + +But here's the problem: most developers are trying to build autonomous systems on infrastructure designed for websites, not agents. + +As [Goldman Sachs' infrastructure analysis](https://www.goldmansachs.com/insights/articles/a-generational-infrastructure-buildout-might-hinge-on-ai-agents) points out: + +> "We're trying to run autonomous systems on infrastructure built for click-and-response websites. It's like trying to run a Tesla on roads designed for horses." + +Traditional cloud platforms (AWS Lambda, Google Cloud Functions, Azure Functions) were optimized for: +- **5ms response times** (agents need minutes or hours to think) +- **Stateless execution** (agents need persistent memory) +- **Edge distribution** (agents need GPU proximity) + +This makes them poorly suited for: +- **Long-running reasoning** processes that require extended compute time +- **Persistent state management** across multiple interactions +- **Complex agent workflows** that span multiple reasoning cycles + +## What Exactly Is an AI Agent? + + +For a comprehensive overview of agents and how they differ from traditional software, see our [What is an Agent?](/Guides/what-is-an-agent) guide. + + +An AI agent is not just another chatbot or API wrapper around an LLM. It's a fundamentally different type of software that combines a LLM with **memory, tools, and a reasoning loop**. + +### The Agent Formula +``` +Agent = LLM + Memory + Tools + Reasoning Loop +``` + +Let's break this down: + +1. **LLM (Large Language Model)**: The "brain" that understands intent and generates responses +2. **Memory**: Both short-term (conversation context) and long-term (persistent knowledge) +3. **Tools**: Capabilities to interact with external systems, APIs, and data sources +4. **Reasoning Loop**: The ability to plan, execute, observe results, and adapt + +### Agents vs. Everything Else + +| Traditional API | Chatbot | AI Agent | +|----------------|---------|----------| +| Waits for commands | Responds to messages | Acts autonomously | +| Returns exactly what you ask | Follows scripted patterns | Figures out how to achieve goals | +| Stateless between calls | Maintains conversation context | Remembers everything, learns over time | +| Deterministic output | Limited variation | Adapts based on context | +| Single request-response | Turn-based conversation | Continuous reasoning and action | + +Think of it this way: +- **APIs** are like vending machines - push button, get result +- **Chatbots** are like scripted receptionists - they can talk, but only follow a script +- **Agents** are like smart assistants - they understand goals and figure out how to achieve them + +## The Paradigm Shift: From Deterministic to Non-Deterministic + + +For deeper insights on this shift, read our [Agent Engineering](/Guides/agent-engineering) guide that covers thinking like an agent builder. + + +Traditional software engineering is built on determinism - given the same input, you always get the same output. We write explicit logic for every scenario: + +```typescript +// Traditional deterministic approach +function processCustomerRequest(requestType: string, data: any) { + if (requestType === 'refund') { + if (data.amount < 100) { + return processRefund(data); + } else { + return escalateToManager(data); + } + } else if (requestType === 'complaint') { + return createTicket(data); + } + // ... hundreds more conditions +} +``` + +Agent engineering embraces non-determinism - the agent interprets intent and figures out the best approach: + +```typescript +// Agent-based approach +const handleCustomerRequest = async (request: string, context: AgentContext) => { + // Agent interprets the request + const intent = await analyzeIntent(request); + + // Agent decides on approach + const plan = await createActionPlan(intent, context.customerHistory); + + // Agent executes with available tools + const result = await executePlan(plan, context.availableTools); + + // Agent learns from outcome + await updateKnowledge(result, context.memory); + + return result; +}; +``` + +This shift requires a new mindset: +- **Design for intent**, not implementation +- **Embrace variability** as a feature, not a bug +- **Think in capabilities**, not functions +- **Trust but verify** - use guardrails and observability + +## Why Agents Need Agent-Native Infrastructure + +[Microsoft's analysis](https://blogs.microsoft.com/blog/2025/05/19/microsoft-build-2025-the-age-of-ai-agents-and-building-the-open-agentic-web/) confirms that "most organizations aren't agent-ready" because their infrastructure wasn't built for autonomous systems. + +### The Infrastructure Mismatch + +Traditional cloud platforms face fundamental limitations when running agents: + +| Traditional Cloud | What Agents Need | The Gap | +|-------------------|------------------|---------| +| 15-second timeouts | Long-running processes | Agents timeout mid-thought | +| Stateless by default | Persistent memory | Agents forget everything | +| Distributed to edge | GPU proximity | High latency to AI models | +| Pay per request | Continuous operation | Costs explode unexpectedly | +| Human-centric monitoring | Agent observability | Can't debug agent decisions | + +Major cloud providers like AWS, Google, and Microsoft are working to adapt their platforms for agents. + +But retrofitting existing infrastructure is like turning a highway into an airport - technically possible, but not optimal. + +## Enter Agentuity: The Agent-Native Cloud + + +Learn more about the Agent-Native Cloud paradigm in our [Agent-Native Cloud](/Guides/agent-native-cloud) guide. + + +While others retrofit, Agentuity was built from day one specifically for agents. Agentuity's agent-native platform puts AI agents at the center of everything. + +### The Agentuity Difference: Purpose-Built for Agents + +Instead of asking "How do we make Lambda work for agents?", we asked "What would infrastructure look like if agents were the only thing that mattered?" + +Agentuity provides what agents actually need: + +- **Long-running processes**: Agents can think for hours, not seconds +- **Persistent memory**: Built-in [key-value](/Guides/key-value), [vector](/Guides/vector-db), and [object storage](/Guides/object-storage) +- **Agent-to-agent communication**: Seamless and secure [channels between agents](/Guides/agent-communication) +- **Native observability**: Track agent decisions with [built-in tracing](/Guides/agent-tracing) +- **Automatic scaling**: Based on agent workload, not request count +- **Framework agnostic**: Run agents on any framework (LangChain, CrewAI, custom, etc.), and combine them in multi-agent projects +- **Easily integrate with popular tools**: Including the Vercel AI SDK for streamlined AI development + +The result is a platform where agents are first-class citizens, not adapted workloads; memory and state are built-in, not bolted on; long-running is the default, not an exception; and agent communication is native, not a hack. + +## Your First Agent: From Zero to Production + +### Environment Setup + + +For detailed setup instructions, see our [Getting Started Guide](/Introduction/getting-started) and [CLI Installation Guide](/CLI/installation). + + +Before we start building, make sure you have: +- Node.js 18+ or Bun +- Basic command line knowledge +- An Agentuity account (free tier is fine) + +Install the Agentuity CLI: + +```bash +# NPM (if you have Node.js) +npm install -g @agentuity/cli + +# Bun (if you have Bun) +bun install -g @agentuity/cli + +# macOS (Homebrew) +brew tap agentuity/tap && brew install agentuity + +# Universal installer (Windows/Linux/macOS) +curl -fsS https://agentuity.sh | sh + +# Verify installation +agentuity --version + +# Login to your account +agentuity auth login +``` + +### Local Development with DevMode + + +DevMode is Agentuity's local development environment that provides instant feedback, complete observability, and a web interface for testing agents. Learn more in our [DevMode Guide](/Guides/devmode). + + +Start DevMode to test your agents locally: + +```bash +agentuity dev +# Opens at http://localhost:3500 with a web interface +``` + +You'll use DevMode throughout the tutorial below to test each step as you build your agent. + +### Build Your First Agent Step-by-Step + +Work through these steps to build your first agent. Each step adds new capabilities - follow along by creating a project (`agentuity create`), or read through to understand the patterns. + +## Step 1: Basic Agent with Schema + + + +In v1, agents use a **two-file pattern**: `agent.ts` for business logic and `route.ts` for HTTP endpoints and triggers. Schemas validate your input and output from the start, providing type safety and runtime validation. + +### agent.ts + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +export const agent = createAgent({ + // Schemas validate input/output (we'll explore these in Module 6) + schema: { + input: z.object({ + name: z.string().optional() // string, but not required + }), + output: z.object({ + message: z.string() + }) + }, + + // Metadata helps identify your agent + metadata: { + name: 'Hello Agent', + description: 'Simple greeting agent for learning v1 basics' + }, + + // Handler receives (c, input) - input is already parsed and validated! + handler: async (c, input) => { + const name = input.name || 'World'; + + // Return data directly - no response.json() needed + return { + message: `Hello, ${name}!` + }; + } +}); +``` + +### route.ts + +```typescript +import { createRouter } from '@agentuity/runtime'; + +export const router = createRouter(); + +// Define an HTTP POST endpoint at /greet +router.post('/greet', async (c) => { + // Get request body + const body = await c.req.json(); + + // Call the agent - type-safe! + const result = await c.agent.helloAgent.run(body); + + // Return the result + return c.json(result); +}); +``` + +**What this demonstrates:** +- **Two-file pattern**: `agent.ts` for logic, `route.ts` for HTTP endpoints +- **`createAgent()`** replaces default export function from v0 +- **Schemas** define expected input/output types with Zod +- **Handler signature**: `(c, input)` instead of v0's `(request, response, context)` +- **Input is pre-validated** by schema (no manual parsing needed) +- **Direct return values** (not `response.json()`) +- **Routes use `c.agent.helloAgent.run()`** for type-safe agent calls + +**Try it:** +1. Create a new project: `npx @agentuity/cli create` +2. Add both files to `src/agents/hello-agent/` +3. Start DevMode: `npx agentuity dev` +4. Send a POST request to `/greet` with `{"name": "Alice"}` +5. Try without a name: `{}` (uses default "World") +6. Observe the response structure + +**Key insight:** + + +**Schemas are the foundation of v1.** Notice how `input.name` is type-safe - your editor knows it exists and is optional. The schema validates data at runtime AND provides TypeScript types. We'll dive deeper into schemas in Module 6. + + + +**The Two-File Pattern** + +v1 separates concerns: +- `agent.ts` = Business logic (what the agent does) +- `route.ts` = Triggers & routing (how to call the agent) + +This makes agents reusable - the same agent can be called from HTTP routes, cron jobs, email handlers, or other agents! + + + + +## Step 2: With Logging + + + +Add observability with `c.logger` to track what your agent is doing. Logs appear automatically in DevMode and the dashboard. + +### agent.ts + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +export const agent = createAgent({ + schema: { + input: z.object({ name: z.string().optional() }), + output: z.object({ message: z.string() }) + }, + metadata: { + name: 'Hello Agent', + description: 'Greeting agent with logging' + }, + handler: async (c, input) => { + // Log when agent starts + c.logger.info('Hello agent received a request'); + + const name = input.name || 'World'; + + // Log the specific action + c.logger.info(`Greeting ${name}`); + + return { message: `Hello, ${name}!` }; + } +}); +``` + +### route.ts + +```typescript +import { createRouter } from '@agentuity/runtime'; + +export const router = createRouter(); + +router.post('/greet', async (c) => { + const body = await c.req.json(); + const result = await c.agent.helloAgent.run(body); + return c.json(result); +}); +``` + +**What this demonstrates:** +- Using `c.logger.info()` for observability +- Logs appear in DevMode console and dashboard +- Structured logging helps debug and monitor agents + +**Try it:** +1. Update your `agent.ts` with the logging code +2. Call the agent via `/greet` endpoint +3. Open DevMode logs tab +4. See both log messages appear with timestamps +5. Notice how logs include context automatically (sessionId, agentName) + +**Key insight:** + + +**Observability is critical for agents.** Unlike deterministic code, agents make decisions that vary based on input and context. Logs help you understand what your agent is thinking and doing. + + + + +## Step 3: With KV Storage + + + +Use `c.kv` for persistent storage to make your agent remember state across invocations. This greeting agent will count how many times it's been called. + + +We're using KV storage here to make our agent stateful. In **Module 3**, you'll learn about different memory scopes (request/thread/session), and in **Module 4**, we'll explore all storage APIs (KV, Vector, Object) in depth. + + +### agent.ts + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +export const agent = createAgent({ + schema: { + input: z.object({ name: z.string().optional() }), + output: z.object({ + message: z.string(), + greetingNumber: z.number(), + timestamp: z.string() + }) + }, + metadata: { + name: 'Hello Agent', + description: 'Greeting agent with memory' + }, + handler: async (c, input) => { + c.logger.info('Hello agent received a request'); + + const name = input.name || 'World'; + + // Get current greeting count from KV storage + const counterResult = await c.kv.get('stats', 'greeting_count'); + + let count: number; + if (counterResult.exists) { + // Counter exists - increment it + count = await counterResult.data.json(); + count++; + } else { + // First time - start at 1 + count = 1; + } + + // Update the counter in storage + await c.kv.set('stats', 'greeting_count', count); + + c.logger.info(`Greeting #${count} for ${name}`); + + return { + message: `Hello, ${name}!`, + greetingNumber: count, + timestamp: new Date().toISOString() + }; + } +}); +``` + +### route.ts + +```typescript +import { createRouter } from '@agentuity/runtime'; + +export const router = createRouter(); + +router.post('/greet', async (c) => { + const body = await c.req.json(); + const result = await c.agent.helloAgent.run(body); + return c.json(result); +}); +``` + +**What this demonstrates:** +- `c.kv.get(bucket, key)` retrieves data +- `counterResult.exists` checks if data was found +- `c.kv.set(bucket, key, value)` stores data +- Data persists across agent invocations +- Agents can maintain state over time + +**Try it:** +1. Update your agent with the storage code +2. Call the agent multiple times +3. Watch the `greetingNumber` increment +4. Restart DevMode - counter persists! +5. Check the KV storage tab in DevMode + +**Key insight:** + + +**Agents need memory to be useful.** Without storage, every request starts from scratch. With KV storage, your agent can remember state, cache data, and build context over time. We'll explore state management deeply in Module 3. + + + +**KV Storage Quick Reference** + +```typescript +c.kv.get(bucket, key) // Retrieve data +c.kv.set(bucket, key, value) // Store data +c.kv.delete(bucket, key) // Remove data +``` + +We'll cover TTL (auto-expiration) in Module 3! + + + + +## Step 4: Error Handling - Manual vs Schema Validation + + + +Let's compare manual validation to schema validation to see why schemas are fundamental to v1. + +### Approach 1: Manual Validation (The Old Way) + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +export const agent = createAgent({ + schema: { + input: z.object({ name: z.string().optional() }), + output: z.object({ + message: z.string(), + greetingNumber: z.number(), + timestamp: z.string() + }) + }, + metadata: { + name: 'Hello Agent', + description: 'Greeting agent with manual validation' + }, + handler: async (c, input) => { + c.logger.info('Hello agent received a request'); + + // Manual validation (even though schema handles it!) + if (input.name !== undefined && input.name.trim().length === 0) { + c.logger.warn('Empty name provided'); + throw new Error('Name cannot be empty string'); + } + + const name = input.name || 'World'; + + // Get counter with error handling + let count = 1; + try { + const counterResult = await c.kv.get('stats', 'greeting_count'); + if (counterResult.exists) { + count = await counterResult.data.json(); + count++; + } + await c.kv.set('stats', 'greeting_count', count); + } catch (error) { + c.logger.error('Storage error, using default count', { error }); + // Continue with count = 1 instead of failing + } + + c.logger.info(`Greeting #${count} for ${name}`); + + return { + message: `Hello, ${name}!`, + greetingNumber: count, + timestamp: new Date().toISOString() + }; + } +}); +``` + +### Approach 2: Schema Validation (The v1 Way) + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +export const agent = createAgent({ + schema: { + // Schema handles validation automatically! + input: z.object({ + name: z.string().min(1, 'Name cannot be empty').optional() + }), + output: z.object({ + message: z.string(), + greetingNumber: z.number(), + timestamp: z.string() + }) + }, + metadata: { + name: 'Hello Agent', + description: 'Greeting agent with schema validation' + }, + handler: async (c, input) => { + // No manual validation needed - schema rejects invalid input before handler runs + + c.logger.info('Hello agent received a request'); + + const name = input.name || 'World'; + + // Still handle storage errors gracefully + let count = 1; + try { + const counterResult = await c.kv.get('stats', 'greeting_count'); + if (counterResult.exists) { + count = await counterResult.data.json(); + count++; + } + await c.kv.set('stats', 'greeting_count', count); + } catch (error) { + c.logger.error('Storage error, using default count', { error }); + } + + c.logger.info(`Greeting #${count} for ${name}`); + + return { + message: `Hello, ${name}!`, + greetingNumber: count, + timestamp: new Date().toISOString() + }; + } +}); +``` + +### route.ts + +```typescript +import { createRouter } from '@agentuity/runtime'; + +export const router = createRouter(); + +router.post('/greet', async (c) => { + const body = await c.req.json(); + const result = await c.agent.helloAgent.run(body); + return c.json(result); +}); +``` + +**What this demonstrates:** +- Manual validation requires explicit checks and error throwing +- Schema validation happens automatically before handler runs +- Invalid input is rejected with clear error messages +- Defensive error handling for external systems (storage) +- Graceful degradation (continue with defaults on non-critical errors) + +**Try it:** +1. First: Use manual validation version + - Send `{"name": ""}` (empty string) + - See the error from your manual check +2. Then: Use schema validation version + - Send `{"name": ""}` again + - See the schema error (cleaner, automatic!) +3. Compare the error messages +4. Try valid input to confirm it still works + +**Key insight:** + + +**Schemas prevent invalid data from reaching your handler.** Instead of writing manual validation code, define your expectations in the schema. Invalid requests are rejected automatically with helpful error messages. This is why we introduce schemas in Step 1 - they're not optional in v1, they're foundational. + + + +**Schema Validation vs Manual Validation** + +**Manual:** +- Write validation code yourself +- Error handling scattered in handler +- Easy to miss edge cases + +**Schema:** +- Declare requirements once +- Automatic validation before handler +- TypeScript types inferred from schema +- Clear, consistent error messages + +We'll master schemas in Module 6! + + + + +## Lab: Complete Hello-World Agent + +Ready to see everything in action? This lab combines all the concepts you've learned - schemas, logging, storage, and error handling - into a production-ready greeting agent. + +**Features:** +- ✅ Schema validation (input/output) +- ✅ Structured logging (all operations logged) +- ✅ KV storage (persistent greeting counters) +- ✅ Error handling (graceful degradation) +- ✅ Multiple input modes (JSON or plain text) +- ✅ Per-user statistics tracking + + + +This lab project includes: +- Global greeting counter (tracks total greetings) +- Per-user statistics (remembers individual users) +- Multiple routes (JSON, text, health check) +- Comprehensive error handling +- Complete `agentuity.yaml` configuration + +### Testing Your Lab Agent + +After cloning the lab, try the following: +1. Run `agentuity dev` to start DevMode +2. Test basic greeting: `POST /greet` with `{"name": "Alice"}` +3. Test custom greeting: `{"name": "Bob", "greeting": "hey"}` +4. Call multiple times with same name to see personal stats +5. Try plain text endpoint: `POST /greet/text` with body `"Charlie"` +6. Check health: `GET /health` +7. Monitor the logs to see detailed operation tracking +8. Check the KV storage tab to see counters + + +This complete implementation shows patterns you'll use throughout the training modules. Notice how we handle errors gracefully, store data efficiently, and provide rich observability. + + +## Key Takeaways + +- **Agents are different**: They're autonomous systems, not just API wrappers around LLMs +- **The market is massive**: $47B by 2030, with many developers already building agents (you're now one of them!) +- **Infrastructure matters**: Traditional cloud wasn't built for agents' unique needs +- **Non-determinism is a feature**: Agents adapt and reason, they don't just execute +- **Agentuity is agent-native**: Purpose-built infrastructure for agents, not retrofitted +- **Schemas are foundational**: Use them from day one for type safety and validation +- **Two-file pattern**: Separate business logic (`agent.ts`) from routing (`route.ts`) +- **Observability is critical**: Logs help understand what non-deterministic agents are doing +- **Memory enables statefulness**: Agents remember context across interactions + +## What's Next? + +You've just built and deployed your first agent on infrastructure designed specifically for agents. In the next module, we'll dive deeper into the anatomy of an agent - understanding the two-file pattern, routing, triggers, and how agents really work under the hood. + +But first, take a moment to experiment with your agent. Try: +- Calling it with different names and greetings +- Checking the logs in the DevMode interface +- Calling it multiple times to see the counter increment +- Testing error cases like empty names +- Monitoring the KV storage in the dashboard + +Remember: you're learning more than just a new framework. You're learning a fundamentally new way to build software. Welcome to the age of agents! + +--- + +**Ready for Module 2?** [The Anatomy of an Agent](./02-anatomy-of-an-agent) diff --git a/content/v1/Training/02-anatomy-of-an-agent/index.mdx b/content/v1/Training/02-anatomy-of-an-agent/index.mdx new file mode 100644 index 00000000..131757f9 --- /dev/null +++ b/content/v1/Training/02-anatomy-of-an-agent/index.mdx @@ -0,0 +1,347 @@ +--- +title: "Module 2: Anatomy of an Agent" +description: HTTP Routing & Agent Anatomy - Master the two-file pattern, HTTP routing, and the context object +--- + +Now that you've built your first agent, let's understand how agents work under the hood. In this module, you'll master the two-file pattern, HTTP routing fundamentals, and the powerful context object that gives your agents access to storage, logging, and more. + +## The Agent Lifecycle + +Every agent interaction in v1 follows a predictable lifecycle. Understanding this flow is essential for building effective agents. + +``` +Trigger → Route → Schema Validation → Agent Handler → Response +``` + +**Each stage explained:** + +- **Trigger:** How the agent is invoked (HTTP request, cron schedule, email, SMS, etc.) +- **Route:** The router matches the trigger to a handler in `route.ts` +- **Schema Validation:** Input is validated against your schema before the handler runs +- **Agent Handler:** Your business logic executes in `agent.ts` +- **Response:** The return value is sent back to the caller + +This lifecycle applies to all agents, whether you're using raw LLM APIs (OpenAI, Anthropic), agent frameworks (LangChain, CrewAI), or custom logic. Agentuity provides the infrastructure (triggers, routing, storage), you provide the intelligence (planning, reasoning, decision-making). + +### The Two-File Pattern + +In v1, every agent uses a two-file structure that separates concerns: + +**`agent.ts`** - Business logic only +- Schema definitions +- Metadata (name, description) +- Handler function (your core logic) +- No HTTP knowledge + +**`route.ts`** - HTTP orchestration +- Route definitions (GET, POST, etc.) +- Request parsing +- Calling the agent +- Response formatting + +Routes invoke agents via `c.agent.agentName.run(input)`. The SDK auto-discovers agents based on your file structure, providing type-safe calls throughout. + +| Aspect | v0 (Single File) | v1 (Two Files) | +|--------|------------------|----------------| +| **Structure** | Default export function | `agent.ts` + `route.ts` | +| **HTTP Logic** | Mixed with business logic | Separated in `route.ts` | +| **Reusability** | Limited to one trigger type | Same agent, multiple routes | +| **Testing** | Must mock HTTP | Test business logic directly | + +### Planning & Reasoning + +What separates agents from simple scripts is their ability to plan and reason. While Agentuity provides the infrastructure, you implement the logic that makes your agents intelligent. + +Effective agent planning involves: +- **Intent Recognition**: Understanding what the user actually wants +- **Task Decomposition**: Breaking complex requests into smaller steps +- **Resource Assessment**: Determining what tools and data are needed +- **Execution Strategy**: Deciding the order and approach for each step + +Agents use a reasoning loop: **Observe → Think → Act → Reflect**. This iterative approach allows agents to handle uncertainty and recover from errors. + +For a deep dive on agent planning patterns, see the [Agent Engineering Guide](/Guides/agent-engineering). + +--- + +## Tutorial Steps + +Each step below focuses on one core concept with runnable code you can test immediately. + +### Step 1: The Two-File Pattern + + + +Every v1 agent separates business logic from HTTP concerns. Let's see how this works with a simple task agent. + + + +**What this demonstrates:** +- Agent contains only business logic (no HTTP knowledge) +- Routes handle HTTP concerns (parsing, calling agent, formatting) +- Type-safe agent calls via `c.agent.taskAgent.run()` +- Schema validation happens automatically before the handler runs + +**Try it:** +1. Start DevMode: `agentuity dev` +2. Send a POST request: + ```bash + curl -X POST http://localhost:3500/tasks \ + -H "Content-Type: application/json" \ + -d '{"action": "create", "task": "Learn v1 routing"}' + ``` +3. Expected response: + ```json + { + "success": true, + "message": "Created: Learn v1 routing" + } + ``` +4. Check the logs to see the handler execution + +> **Key Insight:** The two-file pattern separates HTTP concerns from business logic. Routes orchestrate, agents execute. This makes testing easier and logic reusable across different trigger types. + + + +--- + +### Step 2: HTTP Routing Patterns + + + +A single agent can serve multiple HTTP routes using different methods, parameters, and query strings. Let's build a complete REST API. + + + +**What this demonstrates:** +- REST semantics: GET for queries, POST for creation, PUT for updates, DELETE for removal +- Route parameters: `c.req.param('id')` extracts values from the URL path +- Query strings: `c.req.query('status')` extracts values from the query +- One agent handles all operations through different routes + +**Try it:** +1. List all tasks: + ```bash + curl http://localhost:3500/tasks + ``` +2. Filter with query string: + ```bash + curl http://localhost:3500/tasks?status=completed + ``` +3. Get specific task: + ```bash + curl http://localhost:3500/tasks/123 + ``` +4. Create a task: + ```bash + curl -X POST http://localhost:3500/tasks \ + -H "Content-Type: application/json" \ + -d '{"title": "New task", "status": "pending"}' + ``` +5. Update a task: + ```bash + curl -X PUT http://localhost:3500/tasks/123 \ + -H "Content-Type: application/json" \ + -d '{"title": "Updated task", "status": "completed"}' + ``` +6. Delete a task: + ```bash + curl -X DELETE http://localhost:3500/tasks/123 + ``` + +> **Key Insight:** REST principles apply to agent routes. Use GET for queries, POST for creation, PUT for updates, DELETE for removal. Route parameters and query strings make your APIs intuitive and semantic. + + + +--- + +### Step 3: The Context Object + + + +The context object (`c`) is your agent's interface to Agentuity's capabilities. It provides request/response methods, storage, logging, and more. + +#### Context Capabilities + +**Request & Response** +```typescript +// Request data +c.req.json() // Parse JSON body +c.req.text() // Get text body +c.req.param('id') // Route parameter +c.req.query('filter') // Query string parameter + +// Response +c.json(data) // JSON response +c.text(data) // Text response +c.html(data) // HTML response +``` + +**Storage (Basic Introduction)** +```typescript +// Key-value storage for basic caching +await c.kv.get(bucket, key) +await c.kv.set(bucket, key, value, { ttl }) +await c.kv.delete(bucket, key) + +// Note: Vector and object storage covered in Module 5 +``` + +**Observability** +```typescript +// Structured logging +c.logger.info('message', { metadata }) +c.logger.warn('message', { metadata }) +c.logger.error('message', { metadata }) + +// Child loggers (add context to all logs) +const userLogger = c.logger.child({ userId: '123' }) +userLogger.info('User action') // Includes userId automatically +``` + +**Metadata** +```typescript +c.agentName // Current agent name +c.sessionId // Current session ID +c.env // Environment variables +``` + +#### When to Use What + +| Scenario | Use | +|----------|-----| +| Get validated input from agent call | `input` parameter in handler | +| Parse HTTP request body | `c.req.json()`, `c.req.text()` | +| Access route parameters | `c.req.param()` | +| Access query strings | `c.req.query()` | +| Log with context | `c.logger` | +| Basic caching | `c.kv` | + +#### Practical Example + + + +**What this demonstrates:** +- Extracting route parameters and query strings +- Creating child loggers with user context +- Basic caching with KV storage (5-minute TTL) +- Logging cache hits and misses +- Type-safe agent calls + +**Try it:** +1. First request (cache miss): + ```bash + curl http://localhost:3500/users/user123/tasks?filter=pending + ``` +2. Second request within 5 minutes (cache hit): + ```bash + curl http://localhost:3500/users/user123/tasks?filter=pending + ``` +3. Check logs to see cache behavior + +> **Key Insight:** The context object is your toolbox. Use `c.req` for HTTP details, `c.logger` for observability, `c.kv` for basic caching, and `c.agent` for multi-agent workflows. Each capability is designed to work together seamlessly. + + + +--- + +### Step 4: Request/Response Handling + + + +Agents can handle different input formats and return different response types. This flexibility makes your agents versatile and easy to integrate. + + + +**What this demonstrates:** +- Parsing different input formats (JSON, text, FormData) +- Returning different response types (JSON, text, HTML, redirect) +- Content negotiation based on Accept header +- Custom status codes and headers + +**Try it:** +1. JSON input: + ```bash + curl -X POST http://localhost:3500/tasks/json \ + -H "Content-Type: application/json" \ + -d '{"action": "create", "title": "New task"}' + ``` +2. Plain text input: + ```bash + curl -X POST http://localhost:3500/tasks/text \ + -H "Content-Type: text/plain" \ + -d "Buy groceries" + ``` +3. Form data input: + ```bash + curl -X POST http://localhost:3500/tasks/form \ + -F "title=Clean room" \ + -F "status=pending" + ``` +4. Content negotiation (HTML response): + ```bash + curl http://localhost:3500/tasks/123 \ + -H "Accept: text/html" + ``` +5. Content negotiation (plain text response): + ```bash + curl http://localhost:3500/tasks/123 \ + -H "Accept: text/plain" + ``` +6. Default JSON response: + ```bash + curl http://localhost:3500/tasks/123 + ``` + +> **Key Insight:** Flexible I/O makes agents versatile. Support multiple input formats to integrate with any system. Return appropriate response types based on client needs. Content negotiation enables one endpoint to serve multiple formats. + + + +--- + +## Key Takeaways + +By the end of this module, you should understand: + +1. **The Two-File Pattern:** + - `agent.ts` contains business logic + - `route.ts` contains HTTP endpoints + - Routes call agents via `c.agent.name.run()` + +2. **HTTP Routing:** + - Different methods (GET, POST, PUT, DELETE) + - Route parameters and query strings + - Multiple routes for one agent + - REST principles + +3. **Context Object (`c`):** + - Request/response methods + - Logging capabilities + - Basic KV storage for caching + - Metadata access + +4. **Request/Response Patterns:** + - Multiple input formats (JSON, text, FormData) + - Multiple response types (JSON, text, HTML) + - Content negotiation + - Custom headers and status codes + +You can now build production HTTP APIs with proper routing, validation, and basic storage. This foundation prepares you for specialized routes in Module 3. + +--- + +## What's Next? + +**Module 3: Specialized Routes & Triggers** - Learn cron scheduling, email/SMS triggers, WebSocket/SSE for real-time communication, and multi-trigger architecture patterns. + +For more details on routing patterns and best practices, see the [Routing Guide](/Guides/routing). diff --git a/content/v1/Training/03-specialized-routes-triggers/index.mdx b/content/v1/Training/03-specialized-routes-triggers/index.mdx new file mode 100644 index 00000000..f432a7c2 --- /dev/null +++ b/content/v1/Training/03-specialized-routes-triggers/index.mdx @@ -0,0 +1,243 @@ +--- +title: "Module 3: Specialized Routes & Triggers" +description: Beyond HTTP - Master cron scheduling, email/SMS triggers, real-time routes, and multi-trigger architecture +--- + +You've seen how useful HTTP routing can be, but it's not the only way to interact with your agents. Now we'll learn how to build agents that respond to scheduled jobs, email and SMS messages, and real-time connections. This module covers the specialized triggers that let your agents respond to any event, on any channel. + +## Beyond HTTP + +Agents can respond to more than HTTP requests. The router supports specialized triggers for different interaction patterns, all defined declaratively in code. + +### Trigger Types + +Routes define triggers through code, not configuration files or UI settings. Each route type provides specific capabilities: + +**HTTP** - User-initiated requests (GET, POST, PUT, DELETE) +**Cron** - Scheduled execution with cron expressions +**Email** - Email-based interaction with natural language +**SMS** - Text message triggers for mobile integration +**WebSocket** - Real-time bidirectional communication +**SSE** - Server-to-client streaming for live updates + +### When to Use Each Trigger + +| Trigger | Best For | Example Use Cases | +|---------|----------|-------------------| +| **Cron** | Scheduled tasks | Cache warming, batch processing, scheduled reports, cleanup | +| **Email** | Natural language | Support tickets, queries, automated workflows | +| **SMS** | Mobile alerts | Concise queries, notifications, two-way texting | +| **WebSocket** | Bidirectional real-time | Chat, live collaboration, gaming | +| **SSE** | Server streaming | Live dashboards, progress updates, data feeds | + +### Multi-Trigger Architecture + +Design agents that respond to multiple channels. Share business logic across triggers while implementing channel-specific behavior in routes. + +--- + +## Tutorial Steps + +Each step below focuses on a different trigger type with simple, self-contained examples. + +### Step 1: Status Checker (Cron + HTTP) + + + +Scheduled jobs enable proactive agents. This status checker runs every hour to verify services are up, storing results that can be queried on-demand. + + + +**What this demonstrates:** +- Cron syntax: `'0 * * * *'` runs every hour at minute 0 +- Scheduled data updates with KV storage +- HTTP routes for querying cached data +- Simple loop for updating multiple items + +**Cron Syntax Quick Reference:** +``` +┌─────── minute (0-59) +│ ┌───── hour (0-23) +│ │ ┌─── day of month (1-31) +│ │ │ ┌─ month (1-12) +│ │ │ │ ┌ day of week (0-6, Sunday=0) +* * * * * + +Examples: +'0 * * * *' - Every hour +'0 0 * * *' - Daily at midnight +'*/15 * * * *' - Every 15 minutes +'0 9 * * 1-5' - Weekdays at 9 AM +``` + +For complete cron documentation and best practices, see the [Routing Guide](/Guides/routing). + +**Try it:** +1. Start DevMode: `agentuity dev` +2. Trigger cron manually in DevMode console +3. Check logs to see status updates +4. Query via HTTP: `curl http://localhost:3500/status/api` +5. Notice data comes from cache (updated by cron) + +> **Key Insight:** Scheduled jobs enable proactive agents. Cron updates data periodically, HTTP routes serve cached results. This pattern keeps responses fast while ensuring data freshness. + + + +--- + +### Step 2: Email Responder + + + +Email triggers enable conversational agents. This auto-responder acknowledges incoming emails with formatted text replies. + + + +**What this demonstrates:** +- Email triggers: `router.email('address@example.com')` +- Email parsing: `c.req.email()` provides `from`, `subject`, `text`, `html` +- Text response formatting +- Simple acknowledgment pattern + +**Try it:** +1. Start DevMode +2. Send test email to `hello@example.com` (DevMode provides test endpoint) +3. Check logs to see parsed email data +4. Receive formatted auto-response + +> **Key Insight:** Email triggers enable natural language interaction. Parse email data, process it through your agent, and return formatted text responses. + + + +--- + +### Step 3: Real-Time Echo (WebSocket & SSE) + + + +Real-time routes enable live agents. This example shows both WebSocket (bidirectional) and SSE (server-to-client) patterns. + + + +**What this demonstrates:** +- WebSocket setup: `router.websocket(path, { onOpen, onMessage, onClose })` +- Bidirectional communication: client sends, server sends +- SSE setup: `router.sse(path, handler)` with `c.stream()` +- Unidirectional streaming: server → client only +- Periodic updates with `setInterval` +- Connection lifecycle management and cleanup + +**WebSocket vs SSE:** + +| Feature | WebSocket | SSE | +|---------|-----------|-----| +| **Direction** | Bidirectional (↔) | Server → Client (→) | +| **Use Case** | Chat, gaming, collaboration | Live feeds, dashboards, notifications | +| **Complexity** | More complex | Simpler | +| **Reconnection** | Manual | Automatic | +| **Protocol** | `ws://` or `wss://` | HTTP/HTTPS | + +**Try it:** +1. **WebSocket:** + - Connect: `ws://localhost:3500/echo` + - Send message: `"Hello"` + - Receive echo back with timestamp + +2. **SSE:** + - Connect: `curl -N http://localhost:3500/updates` + - Receive periodic updates every 5 seconds + - Auto-reconnects if connection drops + +> **Key Insight:** WebSocket enables bidirectional communication (client sends, server responds). SSE enables server-to-client streaming (server pushes updates). Use WebSocket for interactive experiences, SSE for live data feeds. + + + +--- + +### Step 4: Multi-Trigger Notifications + + + +Modern agents should be accessible via multiple channels. This notification agent demonstrates how to use different routes for different triggers while sharing business logic. + + + +**What this demonstrates:** +- Different routes for different triggers (recommended pattern) +- HTTP: On-demand notifications +- Cron: Scheduled daily digest +- Email: Email-triggered notifications +- Shared agent logic across all triggers +- Channel-specific response formatting +- Smart caching strategies per trigger + +**Best Practices:** + +**Use different routes when:** +- Logic differs significantly per trigger +- Response format differs (JSON vs email text vs SMS) +- You want clear separation of concerns +- Each trigger has unique caching or rate limiting needs + +**Design guidelines:** +- Log trigger type for observability +- Use trigger-specific caching strategies +- Format responses appropriately per channel +- Share agent business logic across all routes +- Document expected behavior per trigger + +> **Key Insight:** Design for multi-channel access. Use different routes for different triggers with shared agent logic. This makes your agents flexible and user-friendly across all channels while maintaining clean code organization. + + + +--- + +## Key Takeaways + +By the end of this module, you should understand: + +1. **Cron Scheduling:** + - Cron syntax for scheduling tasks + - Scheduled data updates with KV storage + - Periodic maintenance patterns + - Combining cron with HTTP for fast queries + +2. **Email Triggers:** + - Email route setup with `router.email()` + - Parsing email data (`c.req.email()`) + - Text response formatting + - Natural language interaction patterns + +3. **Real-Time Routes:** + - WebSocket for bidirectional communication + - SSE for server-to-client streaming + - Connection lifecycle management + - When to use each pattern + +4. **Multi-Trigger Architecture:** + - Different routes for different triggers + - Shared agent logic across channels + - Trigger-specific behavior in routes + - Best practices for multi-channel agents + +Agents can now respond to any channel. Design routes around user needs, not technical constraints. + +--- + +## What's Next? + +**Module 4: State Management** - Learn about `c.state`, `c.thread.state`, and `c.session.state` for managing memory across different scopes (request, conversation, user-level). + +For more details on routing patterns an \ No newline at end of file diff --git a/content/v1/Training/04-state-management/index.mdx b/content/v1/Training/04-state-management/index.mdx new file mode 100644 index 00000000..b6337fdf --- /dev/null +++ b/content/v1/Training/04-state-management/index.mdx @@ -0,0 +1,291 @@ +--- +title: "Module 4: State Management" +description: Managed memory scopes for requests, conversations, and users +--- + +Agents need memory to be useful. The SDK provides three managed state scopes: request, thread, and session, each designed for different data lifetimes and use cases. This module teaches you when and how to use each scope. + +## The State Management Challenge + +Without proper state management, agents lose context between requests. Users must repeat information, agents can't personalize responses, and conversations feel disconnected. State management solves this by providing structured memory scopes that match real-world interaction patterns. + +**Core question:** *"What should my agent remember, and for how long?"* + +## Understanding the Three State Scopes + +The SDK provides three distinct state scopes, each optimized for different memory patterns: + +| Scope | Lifetime | Cleared When | Access | Use Case | +|-------|----------|--------------|--------|----------| +| **Request** | Single request | After response sent | `c.state` | Timing, temp calculations | +| **Thread** | Up to 1 hour | Thread expiration or destroy | `c.thread.state` | Conversation history | +| **Session** | Spans threads | In-memory (provider dependent) | `c.session.state` | User preferences | + +### Request State (`c.state`) +Ephemeral data that exists only during the current request. Automatically cleared when the response is sent. Use for temporary calculations, timing metrics, or request-specific context. + +**Example use cases:** +- Request timing and performance metrics +- Temporary calculations within handler execution +- Request-specific flags or settings + +### Thread State (`c.thread.state`) +Conversation context that persists across multiple requests for up to 1 hour. Threads are identified by a unique thread ID stored in a cookie, enabling conversation continuity. + +**Example use cases:** +- Chat conversation history +- Multi-turn workflow state +- Conversation-specific preferences + +### Session State (`c.session.state`) +User-level data that persists across multiple threads and conversations. Stored in-memory during the session lifecycle. + +**Example use cases:** +- User preferences and settings +- Cross-conversation tracking +- User-specific context + + +**State vs Storage:** This module focuses on **managed state scopes** with automatic lifecycle handling. Module 5 covers **storage APIs** (`c.kv`, `c.vector`, `c.objectstore`) for explicit control over data persistence, custom TTL, and shared data *across agents*. + + +--- + +## Tutorial Steps + +Each step below focuses on one state scope with simple, runnable code you can test immediately. + +### Step 1: Request State + + + +Request state provides ephemeral, request-scoped memory. Data stored in `c.state` is automatically cleared after the response is sent, making it perfect for temporary calculations and timing metrics. + + + +**What this demonstrates:** +- Using `c.state.set()` and `c.state.get()` for temporary data +- Request timing measurement pattern +- Automatic cleanup (state cleared after response) +- State isolation between requests + +**Try it:** +1. Start DevMode: `agentuity dev` +2. Send a task with timing: + ```bash + curl -X POST http://localhost:3500/process \ + -H "Content-Type: application/json" \ + -d '{"task": "analyze-data", "simulateDelay": true}' + ``` +3. Expected response: + ```json + { + "result": "Completed: analyze-data", + "executionTime": 102, + "timestamp": "2025-01-19T..." + } + ``` +4. Send multiple requests - each has isolated state +5. Check logs to see execution timing + +> **Key Insight:** Request state is ephemeral and request-scoped. Use it for temporary data that doesn't need to persist beyond the current request. + + + +--- + +### Step 2: Thread State + + + +Thread state maintains conversation context across multiple requests for up to 1 hour. Threads are identified by a unique ID stored in a cookie, enabling conversation continuity without manual session management. + + + +**What this demonstrates:** +- Using `c.thread.state` for conversation history +- Thread ID tracking with `c.thread.id` +- Data persistence across requests (up to 1 hour) +- Manual thread reset with `c.thread.destroy()` + +**Try it:** +1. Send first message: + ```bash + curl -X POST http://localhost:3500/chat \ + -H "Content-Type: application/json" \ + -d '{"message": "Hello"}' + ``` +2. Send second message - see conversation history: + ```bash + curl -X POST http://localhost:3500/chat \ + -H "Content-Type: application/json" \ + -d '{"message": "How are you?"}' + ``` +3. Expected response shows both messages: + ```json + { + "response": "Message 2 received", + "messageCount": 2, + "threadId": "thrd_abc123...", + "conversationHistory": ["Hello", "How are you?"] + } + ``` +4. Reset conversation: + ```bash + curl -X POST http://localhost:3500/chat \ + -H "Content-Type: application/json" \ + -d '{"reset": true}' + ``` +5. View conversation history: + ```bash + curl http://localhost:3500/chat/history + ``` + +> **Key Insight:** Thread state persists conversation context for up to 1 hour automatically. Use `c.thread.destroy()` to manually reset conversations. + + + +--- + +### Step 3: Session State + + + +Session state stores user-level data that persists across multiple threads and conversations. Use it for user preferences, settings, and cross-conversation tracking. + + + +**What this demonstrates:** +- Using `c.session.state` for user preferences +- Data persistence across thread resets +- Default values for new users +- Update tracking and counting + +**Try it:** +1. Get preferences (creates default): + ```bash + curl http://localhost:3500/users/user123/preferences + ``` +2. Expected response: + ```json + { + "preferences": { + "name": "Guest", + "theme": "light", + "language": "en" + }, + "updateCount": 0, + "message": "Current preferences retrieved" + } + ``` +3. Update preferences: + ```bash + curl -X POST http://localhost:3500/users/user123/preferences \ + -H "Content-Type: application/json" \ + -d '{"name": "Alice", "theme": "dark"}' + ``` +4. Reset conversation (thread) - preferences persist: + ```bash + curl -X POST http://localhost:3500/chat \ + -H "Content-Type: application/json" \ + -d '{"reset": true}' + ``` +5. Get preferences again - still there: + ```bash + curl http://localhost:3500/users/user123/preferences + ``` + +> **Key Insight:** Session state spans threads, making it perfect for user preferences and cross-conversation data. Thread resets don't affect session state. + + + +--- + +### Step 4: State Lifecycle + + + +Threads expire after 1 hour of inactivity, and you can manually destroy them. Use event listeners to perform cleanup and save important data when threads are destroyed. + + + +**What this demonstrates:** +- Registering cleanup handlers with `c.thread.addEventListener()` +- Thread lifecycle tracking +- Automatic cleanup on expiration +- Manual cleanup with `c.thread.destroy()` + +**Try it:** +1. Send a message: + ```bash + curl -X POST http://localhost:3500/conversation \ + -H "Content-Type: application/json" \ + -d '{"message": "Start conversation"}' + ``` +2. Send more messages - build conversation: + ```bash + curl -X POST http://localhost:3500/conversation \ + -H "Content-Type: application/json" \ + -d '{"message": "Continue conversation"}' + ``` +3. Check thread status: + ```bash + curl http://localhost:3500/conversation/status + ``` +4. Manually reset (triggers cleanup): + ```bash + curl -X POST http://localhost:3500/conversation/reset + ``` +5. Check logs - see cleanup event handler execution + +> **Key Insight:** Use event listeners for cleanup when threads are destroyed. Threads expire automatically after 1 hour, or you can destroy them manually with `c.thread.destroy()`. + + + +--- + +## Key Takeaways + +By the end of this module, you should understand: + +1. **Three State Scopes:** + - Request state (`c.state`) for ephemeral, request-scoped data + - Thread state (`c.thread.state`) for conversation context (up to 1 hour) + - Session state (`c.session.state`) for user preferences (spans threads) + +2. **Automatic Lifecycle:** + - Request state cleared after response + - Thread state expires after 1 hour + - Session state persists across threads + +3. **Thread Management:** + - Threads identified by unique IDs in cookies + - Manual reset with `c.thread.destroy()` + - Cleanup handlers with `addEventListener('destroyed', ...)` + +4. **When to Use Each Scope:** + - Request → temporary calculations, timing + - Thread → conversation history, workflow state + - Session → user preferences, cross-conversation tracking + +Agentuity manages state automatically - you just choose the right scope for your data lifetime needs. + +--- + +## What's Next? + +**Module 5: Storage APIs** - Learn `c.kv`, `c.vector`, and `c.objectstore` for explicit storage control, custom TTL, shared data across agents, and persistence beyond in-memory state. + +For more details on state management patterns and lifecycle handling, see the [Sessions & Threads Guide](/Guides/sessions-threads). diff --git a/content/v1/Training/05-storage-apis/index.mdx b/content/v1/Training/05-storage-apis/index.mdx new file mode 100644 index 00000000..1fa4467e --- /dev/null +++ b/content/v1/Training/05-storage-apis/index.mdx @@ -0,0 +1,381 @@ +--- +title: "Module 5: Storage APIs" +description: Manual storage control for caching, semantic search, and file management +--- + +Agents need more than automatic state management. They need explicit control over data persistence, custom TTL, and the ability to share data across multiple agents and sessions. This module teaches you how to use the three Agentuity storage APIs: key-value, vector, and object storage. + +## Storage vs State + +Module 4 taught you how to use **managed state scopes** (`c.state`, `c.thread.state`, `c.session.state`) with automatic lifecycle handling. This module teaches you how to use **manual storage APIs** for explicit control. + +| Aspect | State (Module 4) | Storage (Module 5) | +|--------|------------------|-------------------| +| **Lifecycle** | Automatic (Agentuity manages) | Manual (you control) | +| **Scope** | Request/thread/session | Custom buckets and keys | +| **TTL** | Fixed (request, 1 hour, session) | Custom (any duration) | +| **Sharing** | Limited to same session/thread | Shared across agents and sessions | +| **Use Case** | Scoped memory patterns | Persistent data, caching, search | + +**When to use state:** +- Request-scoped calculations +- Conversation context (up to 1 hour) +- User preferences within a session + +**When to use storage:** +- Custom TTL requirements (5 minutes, 24 hours, permanent) +- Data shared across multiple agents +- Semantic search and knowledge bases +- File storage and media +- Persistence beyond session lifetime + +**Core question:** *"How do I store, search, and retrieve different types of data?"* + +## Understanding Storage Types + +The SDK provides three storage APIs, each optimized for different data patterns: + +### Key-Value Storage (`c.kv`) +Fast exact-match lookups for structured data. Use TTL for auto-expiring cache, or omit TTL for permanent storage. + +**Use cases:** +- API response caching (with TTL) +- User preferences (without TTL) +- Feature flags and configuration +- Rate limiting counters + +### Vector Storage (`c.vector`) +Semantic similarity search using embeddings. Finds information by meaning rather than keywords. + +**Use cases:** +- Knowledge bases and documentation search +- Semantic search ("find similar products") +- RAG (Retrieval-Augmented Generation) systems +- Long-term agent memory across sessions + +### Object Storage (`c.objectstore`) +Binary file storage with public URL generation for documents, images, and media. + +**Use cases:** +- User-uploaded files +- Generated reports and exports +- Images and media files +- Large datasets and backups + +--- + +## Tutorial Steps + +Each step below focuses on one storage API with simple, runnable code you can test immediately. + +### Step 1: KV Basics with TTL + + + +Key-value storage with TTL (time-to-live) provides automatic cache expiration. Set a TTL to have data automatically deleted after a specified duration, preventing manual cleanup and storage bloat. + + + +**What this demonstrates:** +- Caching pattern: check cache first, compute on miss +- KV storage with custom TTL (5 minutes) +- Cache hit vs miss handling +- Automatic data deserialization (`result.data` is already parsed) + +**Try it:** +1. Start DevMode: `agentuity dev` +2. First request (cache miss): + ```bash + curl -X POST http://localhost:3500/query \ + -H "Content-Type: application/json" \ + -d '{"query": "what is agentuity"}' + ``` +3. Expected response: + ```json + { + "result": "Computed result for: what is agentuity", + "cached": false, + "cacheKey": "query:what-is-agentuity" + } + ``` +4. Second request within 5 minutes (cache hit): + ```bash + curl -X POST http://localhost:3500/query \ + -H "Content-Type: application/json" \ + -d '{"query": "what is agentuity"}' + ``` +5. Expected response: + ```json + { + "result": "Computed result for: what is agentuity", + "cached": true, + "cacheKey": "query:what-is-agentuity" + } + ``` +6. Check logs to see cache hit/miss behavior + +> **Key Insight:** TTL-based caching prevents manual cleanup and storage costs. Data automatically expires after the specified duration. + + + +--- + +### Step 2: KV Persistent Storage + + + +Omit the TTL parameter to make data persist indefinitely until explicitly deleted. Use for user profiles, settings, and application state that should survive restarts. + + + +**What this demonstrates:** +- Persistent storage by omitting TTL parameter +- Default values for new users +- Partial updates (merge with existing data) +- Schema validation with Zod + +**Try it:** +1. Get preferences (creates defaults): + ```bash + curl http://localhost:3500/users/user123/settings + ``` +2. Expected response: + ```json + { + "preferences": { + "theme": "light", + "language": "en", + "notifications": true + }, + "source": "default", + "updated": false + } + ``` +3. Update preferences: + ```bash + curl -X POST http://localhost:3500/users/user123/settings \ + -H "Content-Type: application/json" \ + -d '{"theme": "dark", "notifications": false}' + ``` +4. Get preferences again - changes persist: + ```bash + curl http://localhost:3500/users/user123/settings + ``` +5. Restart DevMode - preferences still there (no TTL = permanent) + +> **Key Insight:** No TTL means data persists forever until explicitly deleted. Use for user profiles, settings, and permanent application state. + + + +--- + +### Step 3: Vector Basics with Variadic Upsert + + + +Vector storage enables semantic search—finding information by meaning rather than keywords. Documents are automatically converted to embeddings for similarity matching. + +**Important API Change:** The upsert operation uses variadic parameters, allowing you to pass multiple documents in a single call. + + + +**What this demonstrates:** +- **Variadic upsert pattern**: `await c.vector.upsert(bucket, ...array)` +- Two approaches: spread array (dynamic) or individual parameters (static) +- Semantic search with query text and similarity threshold +- Automatic embedding generation from document text +- Similarity scores (0-1, where 1 = perfect match) + +**Variadic Upsert Pattern Explained:** + +```typescript +// Option 1: Spread array (recommended for dynamic data) +const documents = [{ key: 'doc1', document: 'text', metadata: {...} }, ...]; +await c.vector.upsert(bucket, ...documents); + +// Option 2: Individual parameters (good for static data) +await c.vector.upsert( + bucket, + { key: 'doc1', document: 'text1', metadata: {...} }, + { key: 'doc2', document: 'text2', metadata: {...} } +); + +// Both are equivalent - choose based on your use case +``` + +**Try it:** +1. Search for "What is Agentuity?": + ```bash + curl -X POST http://localhost:3500/search \ + -H "Content-Type: application/json" \ + -d '{"query": "What is Agentuity?"}' + ``` +2. Expected response (semantic matches): + ```json + { + "query": "What is Agentuity?", + "resultsFound": 3, + "results": [ + { + "key": "fact-1", + "similarity": 0.89, + "category": "platform" + }, + ... + ] + } + ``` +3. Try different queries: + - "How does storage work?" → finds storage-related facts + - "semantic search" → finds vector storage explanation +4. Notice similarity scores vary by relevance + +> **Key Insight:** Vector search finds meaning, not keywords. Use the variadic upsert pattern (`...array`) for efficient bulk inserts. + + + +--- + +### Step 4: Vector Filtering with Metadata + + + +Combine semantic similarity with structured metadata filters to narrow results by category, user, status, or any metadata field. + + + +**What this demonstrates:** +- Adding metadata to vectors (category, price, inStock, etc.) +- Filtering search results with `metadata` parameter +- Metadata filters use AND logic (all conditions must match) +- Lower similarity threshold when using strict filters +- Combining semantic search with business rules + +**Try it:** +1. Search all products: + ```bash + curl -X POST http://localhost:3500/products/search \ + -H "Content-Type: application/json" \ + -d '{"query": "comfortable workspace"}' + ``` +2. Filter by category (furniture only): + ```bash + curl -X POST http://localhost:3500/products/search \ + -H "Content-Type: application/json" \ + -d '{"query": "comfortable workspace", "category": "furniture"}' + ``` +3. Filter by stock status (in-stock only): + ```bash + curl -X POST http://localhost:3500/products/search \ + -H "Content-Type: application/json" \ + -d '{"query": "high performance", "category": "electronics", "inStock": true}' + ``` +4. Compare results with and without filters + +> **Key Insight:** Metadata filtering is exact-match, not fuzzy. Use it to combine semantic search with business rules like availability, category, or user permissions. + + + +--- + +### Step 5: Object Storage for Files + + + +Files and media need different storage than structured data. Object storage handles binary content and provides shareable public URLs with automatic expiration. + + + +**What this demonstrates:** +- Storing text/binary files with `c.objectstore.put()` +- Retrieving files with `c.objectstore.get()` +- Creating temporary public URLs (expires in 1 hour) +- File cleanup with `c.objectstore.delete()` + +**Try it:** +1. Upload and retrieve a file: + ```bash + curl -X POST http://localhost:3500/files/upload \ + -H "Content-Type: application/json" \ + -d '{"content": "Hello from object storage!", "filename": "demo.txt"}' + ``` +2. Expected response: + ```json + { + "message": "File stored, retrieved, and public URL created", + "filename": "demo.txt", + "contentLength": 27, + "publicUrl": "https://...", + "urlExpiresIn": "1 hour" + } + ``` +3. Access the public URL in browser (works for 1 hour) +4. Try uploading different content types + +> **Key Insight:** Public URLs expire automatically, perfect for temporary file sharing without permanent public access. + + + +--- + +## Lab Project + +TODO: translate docs Q&A (RAG) example here + +--- + +## Key Takeaways + +By the end of this module, you should understand: + +1. **Storage vs State:** + - State (Module 4) = Managed scopes with automatic lifecycle + - Storage (Module 5) = Manual control with custom TTL and sharing + +2. **Three Storage APIs:** + - KV = Fast exact-match lookups, optional TTL + - Vector = Semantic search by meaning + - Object = Binary files and public URLs + +3. **KV Storage Patterns:** + - With TTL for auto-expiring cache + - Without TTL for permanent data + - Automatic deserialization of stored values + +4. **Vector Storage Patterns:** + - Variadic upsert: `await c.vector.upsert(bucket, ...documents)` + - Semantic search with similarity thresholds + - Metadata filtering for targeted results + +5. **When to Use Each:** + - KV → Caching, preferences, configuration + - Vector → Knowledge bases, semantic search, RAG + - Object → Files, media, large binary data + +Choose the right storage API for your data type and access pattern. Combine multiple storage types for complex use cases like RAG systems. + +--- + +## What's Next? + +**Module 6: Agent Collaboration** - Learn `c.agent.other.run()` for multi-agent workflows, parallel execution, and orchestration patterns. + +For more details on storage APIs, see: +- [Key-Value Storage Guide](/Guides/key-value-storage) +- [Vector Storage Guide](/Guides/vector-storage) +- [Object Storage Guide](/Guides/object-storage) diff --git a/examples/training/01-intro/step1-basic-agent/agent.ts b/examples/training/01-intro/step1-basic-agent/agent.ts new file mode 100644 index 00000000..46961234 --- /dev/null +++ b/examples/training/01-intro/step1-basic-agent/agent.ts @@ -0,0 +1,30 @@ +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +export const agent = createAgent({ + // Schemas validate input/output (we'll explore these in Module 6) + schema: { + input: z.object({ + name: z.string().optional() // string, but not required + }), + output: z.object({ + message: z.string() + }) + }, + + // Metadata helps identify your agent + metadata: { + name: 'Hello Agent', + description: 'Simple greeting agent for learning v1 basics' + }, + + // Handler receives (c, input) - input is already parsed and validated! + handler: async (c, input) => { + const name = input.name || 'World'; + + // Return data directly - no response.json() needed + return { + message: `Hello, ${name}!` + }; + } +}); diff --git a/examples/training/01-intro/step1-basic-agent/route.ts b/examples/training/01-intro/step1-basic-agent/route.ts new file mode 100644 index 00000000..24c7dc52 --- /dev/null +++ b/examples/training/01-intro/step1-basic-agent/route.ts @@ -0,0 +1,15 @@ +import { createRouter } from '@agentuity/runtime'; + +export const router = createRouter(); + +// Define an HTTP POST endpoint at /greet +router.post('/greet', async (c) => { + // Get request body + const body = await c.req.json(); + + // Call the agent (type-safe) + const result = await c.agent.helloAgent.run(body); + + // Return the result + return c.json(result); +}); diff --git a/examples/training/01-intro/step2-with-logging/agent.ts b/examples/training/01-intro/step2-with-logging/agent.ts new file mode 100644 index 00000000..568e78dd --- /dev/null +++ b/examples/training/01-intro/step2-with-logging/agent.ts @@ -0,0 +1,24 @@ +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +export const agent = createAgent({ + schema: { + input: z.object({ name: z.string().optional() }), + output: z.object({ message: z.string() }) + }, + metadata: { + name: 'Hello Agent', + description: 'Greeting agent with logging' + }, + handler: async (c, input) => { + // Log when agent starts + c.logger.info('Hello agent received a request'); + + const name = input.name || 'World'; + + // Log the specific action + c.logger.info(`Greeting ${name}`); + + return { message: `Hello, ${name}!` }; + } +}); diff --git a/examples/training/01-intro/step2-with-logging/route.ts b/examples/training/01-intro/step2-with-logging/route.ts new file mode 100644 index 00000000..ac371798 --- /dev/null +++ b/examples/training/01-intro/step2-with-logging/route.ts @@ -0,0 +1,9 @@ +import { createRouter } from '@agentuity/runtime'; + +export const router = createRouter(); + +router.post('/greet', async (c) => { + const body = await c.req.json(); + const result = await c.agent.helloAgent.run(body); + return c.json(result); +}); diff --git a/examples/training/01-intro/step3-with-storage/agent.ts b/examples/training/01-intro/step3-with-storage/agent.ts new file mode 100644 index 00000000..3b1c56f7 --- /dev/null +++ b/examples/training/01-intro/step3-with-storage/agent.ts @@ -0,0 +1,46 @@ +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +export const agent = createAgent({ + schema: { + input: z.object({ name: z.string().optional() }), + output: z.object({ + message: z.string(), + greetingNumber: z.number(), + timestamp: z.string() + }) + }, + metadata: { + name: 'Hello Agent', + description: 'Greeting agent with memory' + }, + handler: async (c, input) => { + c.logger.info('Hello agent received a request'); + + const name = input.name || 'World'; + + // Get current greeting count from KV storage + const counterResult = await c.kv.get('stats', 'greeting_count'); + + let count: number; + if (counterResult.exists) { + // Counter exists - increment it + count = await counterResult.data.json(); + count++; + } else { + // First time - start at 1 + count = 1; + } + + // Update the counter in storage + await c.kv.set('stats', 'greeting_count', count); + + c.logger.info(`Greeting #${count} for ${name}`); + + return { + message: `Hello, ${name}!`, + greetingNumber: count, + timestamp: new Date().toISOString() + }; + } +}); diff --git a/examples/training/01-intro/step3-with-storage/route.ts b/examples/training/01-intro/step3-with-storage/route.ts new file mode 100644 index 00000000..ac371798 --- /dev/null +++ b/examples/training/01-intro/step3-with-storage/route.ts @@ -0,0 +1,9 @@ +import { createRouter } from '@agentuity/runtime'; + +export const router = createRouter(); + +router.post('/greet', async (c) => { + const body = await c.req.json(); + const result = await c.agent.helloAgent.run(body); + return c.json(result); +}); diff --git a/examples/training/01-intro/step4-schema-validation/agent.ts b/examples/training/01-intro/step4-schema-validation/agent.ts new file mode 100644 index 00000000..80e7c4ae --- /dev/null +++ b/examples/training/01-intro/step4-schema-validation/agent.ts @@ -0,0 +1,48 @@ +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +export const agent = createAgent({ + schema: { + // Schema handles validation automatically! + input: z.object({ + name: z.string().min(1, 'Name cannot be empty').optional() + }), + output: z.object({ + message: z.string(), + greetingNumber: z.number(), + timestamp: z.string() + }) + }, + metadata: { + name: 'Hello Agent', + description: 'Greeting agent with schema validation' + }, + handler: async (c, input) => { + // No manual validation needed - schema rejects invalid input before handler runs! + + c.logger.info('Hello agent received a request'); + + const name = input.name || 'World'; + + // Still handle storage errors gracefully + let count = 1; + try { + const counterResult = await c.kv.get('stats', 'greeting_count'); + if (counterResult.exists) { + count = await counterResult.data.json(); + count++; + } + await c.kv.set('stats', 'greeting_count', count); + } catch (error) { + c.logger.error('Storage error, using default count', { error }); + } + + c.logger.info(`Greeting #${count} for ${name}`); + + return { + message: `Hello, ${name}!`, + greetingNumber: count, + timestamp: new Date().toISOString() + }; + } +}); diff --git a/examples/training/01-intro/step4-schema-validation/route.ts b/examples/training/01-intro/step4-schema-validation/route.ts new file mode 100644 index 00000000..ac371798 --- /dev/null +++ b/examples/training/01-intro/step4-schema-validation/route.ts @@ -0,0 +1,9 @@ +import { createRouter } from '@agentuity/runtime'; + +export const router = createRouter(); + +router.post('/greet', async (c) => { + const body = await c.req.json(); + const result = await c.agent.helloAgent.run(body); + return c.json(result); +}); diff --git a/examples/training/02-anatomy/step1-two-file-pattern/agent.ts b/examples/training/02-anatomy/step1-two-file-pattern/agent.ts new file mode 100644 index 00000000..bf1ff06a --- /dev/null +++ b/examples/training/02-anatomy/step1-two-file-pattern/agent.ts @@ -0,0 +1,35 @@ +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +export const agent = createAgent({ + schema: { + input: z.object({ + action: z.enum(['create', 'get']), + task: z.string().optional() + }), + output: z.object({ + success: z.boolean(), + message: z.string() + }) + }, + metadata: { + name: 'Task Agent', + description: 'Simple task management' + }, + // Handler contains business logic only + handler: async (c, input) => { + c.logger.info('Task action', { action: input.action }); + + if (input.action === 'create') { + return { + success: true, + message: `Created: ${input.task}` + }; + } + + return { + success: true, + message: 'Task retrieved' + }; + } +}); diff --git a/examples/training/02-anatomy/step1-two-file-pattern/route.ts b/examples/training/02-anatomy/step1-two-file-pattern/route.ts new file mode 100644 index 00000000..f71ea0f6 --- /dev/null +++ b/examples/training/02-anatomy/step1-two-file-pattern/route.ts @@ -0,0 +1,13 @@ +import { createRouter } from '@agentuity/runtime'; + +export const router = createRouter(); + +// Routes handle HTTP concerns: parsing, calling agent, formatting response +router.post('/tasks', async (c) => { + const body = await c.req.json(); + + // Call agent with type-safe access + const result = await c.agent.taskAgent.run(body); + + return c.json(result); +}); diff --git a/examples/training/02-anatomy/step2-http-routing/agent.ts b/examples/training/02-anatomy/step2-http-routing/agent.ts new file mode 100644 index 00000000..a3fe3a92 --- /dev/null +++ b/examples/training/02-anatomy/step2-http-routing/agent.ts @@ -0,0 +1,76 @@ +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +export const agent = createAgent({ + schema: { + input: z.object({ + action: z.enum(['list', 'get', 'create', 'update', 'delete']), + id: z.string().optional(), + title: z.string().optional(), + status: z.enum(['pending', 'completed']).optional(), + filter: z.string().optional() + }), + output: z.object({ + success: z.boolean(), + message: z.string().optional(), + task: z.any().optional(), + tasks: z.array(z.any()).optional() + }) + }, + metadata: { + name: 'Task Agent', + description: 'Complete task management with CRUD operations' + }, + handler: async (c, input) => { + c.logger.info('Task action', { action: input.action, id: input.id }); + + // One agent handles all CRUD operations + switch (input.action) { + case 'list': + c.logger.info('Listing tasks', { filter: input.filter }); + return { + success: true, + tasks: [ + { id: '1', title: 'Task 1', status: 'pending' }, + { id: '2', title: 'Task 2', status: 'completed' } + ].filter(task => !input.filter || task.status === input.filter) + }; + + case 'get': + c.logger.info('Getting task', { id: input.id }); + return { + success: true, + task: { id: input.id, title: 'Example Task', status: 'pending' } + }; + + case 'create': + c.logger.info('Creating task', { title: input.title }); + return { + success: true, + message: `Created task: ${input.title}`, + task: { id: '123', title: input.title, status: input.status || 'pending' } + }; + + case 'update': + c.logger.info('Updating task', { id: input.id, title: input.title }); + return { + success: true, + message: `Updated task ${input.id}`, + task: { id: input.id, title: input.title, status: input.status } + }; + + case 'delete': + c.logger.info('Deleting task', { id: input.id }); + return { + success: true, + message: `Deleted task ${input.id}` + }; + + default: + return { + success: false, + message: 'Unknown action' + }; + } + } +}); diff --git a/examples/training/02-anatomy/step2-http-routing/route.ts b/examples/training/02-anatomy/step2-http-routing/route.ts new file mode 100644 index 00000000..61c33266 --- /dev/null +++ b/examples/training/02-anatomy/step2-http-routing/route.ts @@ -0,0 +1,57 @@ +import { createRouter } from '@agentuity/runtime'; + +export const router = createRouter(); + +// GET with query string: /tasks?status=completed +router.get('/tasks', async (c) => { + const status = c.req.query('status'); + const result = await c.agent.taskAgent.run({ + action: 'list', + filter: status + }); + return c.json(result); +}); + +// GET with route parameter: /tasks/:id +router.get('/tasks/:id', async (c) => { + const id = c.req.param('id'); + const result = await c.agent.taskAgent.run({ + action: 'get', + id + }); + return c.json(result); +}); + +// POST for creation +router.post('/tasks', async (c) => { + const body = await c.req.json(); + const result = await c.agent.taskAgent.run({ + action: 'create', + title: body.title, + status: body.status + }); + return c.json(result); +}); + +// PUT for updates +router.put('/tasks/:id', async (c) => { + const id = c.req.param('id'); + const body = await c.req.json(); + const result = await c.agent.taskAgent.run({ + action: 'update', + id, + title: body.title, + status: body.status + }); + return c.json(result); +}); + +// DELETE for removal +router.delete('/tasks/:id', async (c) => { + const id = c.req.param('id'); + const result = await c.agent.taskAgent.run({ + action: 'delete', + id + }); + return c.json(result); +}); diff --git a/examples/training/02-anatomy/step3-context-object/agent.ts b/examples/training/02-anatomy/step3-context-object/agent.ts new file mode 100644 index 00000000..b4a793f5 --- /dev/null +++ b/examples/training/02-anatomy/step3-context-object/agent.ts @@ -0,0 +1,38 @@ +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +export const agent = createAgent({ + schema: { + input: z.object({ + action: z.enum(['list']), + userId: z.string(), + filter: z.string().optional() + }), + output: z.object({ + success: z.boolean(), + tasks: z.array(z.any()) + }) + }, + metadata: { + name: 'Task Agent', + description: 'Task management with context demonstration' + }, + handler: async (c, input) => { + c.logger.info('Listing tasks for user', { + userId: input.userId, + filter: input.filter + }); + + // Simulate fetching tasks + const tasks = [ + { id: '1', title: 'Task 1', status: 'pending' }, + { id: '2', title: 'Task 2', status: 'completed' }, + { id: '3', title: 'Task 3', status: 'pending' } + ].filter(task => !input.filter || task.status === input.filter); + + return { + success: true, + tasks + }; + } +}); diff --git a/examples/training/02-anatomy/step3-context-object/route.ts b/examples/training/02-anatomy/step3-context-object/route.ts new file mode 100644 index 00000000..8e831a98 --- /dev/null +++ b/examples/training/02-anatomy/step3-context-object/route.ts @@ -0,0 +1,38 @@ +import { createRouter } from '@agentuity/runtime'; + +export const router = createRouter(); + +router.get('/users/:userId/tasks', async (c) => { + // Extract route parameter and query string + const userId = c.req.param('userId'); + const filter = c.req.query('filter'); + + // Child logger adds userId to all subsequent logs + const userLogger = c.logger.child({ userId }); + userLogger.info('Fetching tasks', { filter }); + + // Check KV cache with composite key + const cacheKey = `tasks:${userId}:${filter || 'all'}`; + const cached = await c.kv.get('cache', cacheKey); + + if (cached.exists) { + userLogger.info('Cache hit'); + return c.json(await cached.data.json()); + } + + userLogger.info('Cache miss - calling agent'); + + // Call agent with validated input + const result = await c.agent.taskAgent.run({ + action: 'list', + userId, + filter + }); + + // Cache result for 5 minutes (300 seconds) + await c.kv.set('cache', cacheKey, result, { ttl: 300 }); + + userLogger.info('Tasks delivered and cached'); + + return c.json(result); +}); diff --git a/examples/training/02-anatomy/step4-request-response/agent.ts b/examples/training/02-anatomy/step4-request-response/agent.ts new file mode 100644 index 00000000..f22a7b37 --- /dev/null +++ b/examples/training/02-anatomy/step4-request-response/agent.ts @@ -0,0 +1,47 @@ +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +export const agent = createAgent({ + schema: { + input: z.object({ + action: z.enum(['create', 'get']), + id: z.string().optional(), + title: z.string().optional(), + status: z.enum(['pending', 'completed']).optional() + }), + output: z.object({ + success: z.boolean(), + message: z.string().optional(), + task: z.any().optional() + }) + }, + metadata: { + name: 'Task Agent', + description: 'Task management with flexible I/O' + }, + handler: async (c, input) => { + c.logger.info('Task action', { action: input.action }); + + if (input.action === 'create') { + return { + success: true, + message: `Created task: ${input.title}`, + task: { + id: '123', + title: input.title, + status: input.status || 'pending' + } + }; + } + + // Get action + return { + success: true, + task: { + id: input.id, + title: 'Example Task', + status: 'pending' + } + }; + } +}); diff --git a/examples/training/02-anatomy/step4-request-response/route.ts b/examples/training/02-anatomy/step4-request-response/route.ts new file mode 100644 index 00000000..94ebb867 --- /dev/null +++ b/examples/training/02-anatomy/step4-request-response/route.ts @@ -0,0 +1,143 @@ +import { createRouter } from '@agentuity/runtime'; + +export const router = createRouter(); + +// ===== Different Input Formats ===== + +// Parse JSON body +router.post('/tasks/json', async (c) => { + const body = await c.req.json(); + const result = await c.agent.taskAgent.run({ + action: 'create', + title: body.title, + status: body.status + }); + return c.json(result); +}); + +// Parse plain text +router.post('/tasks/text', async (c) => { + const title = await c.req.text(); + const result = await c.agent.taskAgent.run({ + action: 'create', + title + }); + return c.json(result); +}); + +// Parse form data +router.post('/tasks/form', async (c) => { + const formData = await c.req.formData(); + const title = formData.get('title') as string; + const status = formData.get('status') as 'pending' | 'completed'; + + const result = await c.agent.taskAgent.run({ + action: 'create', + title, + status + }); + return c.json(result); +}); + +// ===== Different Response Types ===== + +// Return JSON +router.get('/tasks/:id/json', async (c) => { + const id = c.req.param('id'); + const result = await c.agent.taskAgent.run({ + action: 'get', + id + }); + return c.json(result); +}); + +// Return plain text +router.get('/tasks/:id/text', async (c) => { + const id = c.req.param('id'); + const result = await c.agent.taskAgent.run({ + action: 'get', + id + }); + + const text = `Task ${id}: ${result.task?.title}`; + return c.text(text); +}); + +// Return HTML +router.get('/tasks/:id/html', async (c) => { + const id = c.req.param('id'); + const result = await c.agent.taskAgent.run({ + action: 'get', + id + }); + + const html = ` + + + Task ${id} + +

${result.task?.title}

+

Status: ${result.task?.status}

+ + + `; + return c.html(html); +}); + +// Redirect example +router.get('/task', async (c) => { + const id = c.req.query('id'); + if (!id) { + return c.redirect('/tasks'); + } + return c.redirect(`/tasks/${id}`); +}); + +// ===== Content Negotiation ===== + +// Respond based on Accept header +router.get('/tasks/:id', async (c) => { + const id = c.req.param('id'); + const accept = c.req.header('Accept'); + const result = await c.agent.taskAgent.run({ + action: 'get', + id + }); + + // Check what format client wants + if (accept?.includes('text/html')) { + return c.html(`

${result.task?.title}

`); + } else if (accept?.includes('text/plain')) { + return c.text(result.task?.title || ''); + } else { + return c.json(result); + } +}); + +// ===== Custom Status Codes & Headers ===== + +router.get('/tasks/:id/status', async (c) => { + const id = c.req.param('id'); + + try { + const result = await c.agent.taskAgent.run({ + action: 'get', + id + }); + + // Return with custom headers + return c.json(result, { + status: 200, + headers: { + 'X-Task-Version': '1.0', + 'Cache-Control': 'public, max-age=300' + } + }); + } catch (error) { + // Return 404 with error message + return c.json( + { error: 'Task not found' }, + { status: 404 } + ); + } +}); diff --git a/examples/training/03-specialized-routes/step1-status-checker/agent.ts b/examples/training/03-specialized-routes/step1-status-checker/agent.ts new file mode 100644 index 00000000..7528bf3a --- /dev/null +++ b/examples/training/03-specialized-routes/step1-status-checker/agent.ts @@ -0,0 +1,36 @@ +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +export const agent = createAgent({ + schema: { + input: z.object({ + service: z.string().optional() + }), + output: z.object({ + service: z.string(), + status: z.string(), + lastCheck: z.string() + }) + }, + metadata: { + name: 'Status Checker', + description: 'Check service status' + }, + handler: async (c, input) => { + const service = input.service || 'api'; + + // Get status from KV (updated by cron) + const cached = await c.kv.get('status', service); + + if (cached.exists) { + return await cached.data.json(); + } + + // Default status if not in cache + return { + service, + status: 'up', + lastCheck: new Date().toISOString() + }; + } +}); diff --git a/examples/training/03-specialized-routes/step1-status-checker/route.ts b/examples/training/03-specialized-routes/step1-status-checker/route.ts new file mode 100644 index 00000000..ef482883 --- /dev/null +++ b/examples/training/03-specialized-routes/step1-status-checker/route.ts @@ -0,0 +1,31 @@ +import { createRouter } from '@agentuity/runtime'; + +export const router = createRouter(); + +// HTTP: Query current status +router.get('/status/:service', async (c) => { + const service = c.req.param('service'); + const result = await c.agent.statusChecker.run({ service }); + return c.json(result); +}); + +// CRON: Update status every hour +// Cron expression: '0 * * * *' = minute 0 of every hour +router.cron('0 * * * *', async (c) => { + c.logger.info('Cron: Updating service status'); + + const services = ['api', 'database', 'cache']; + + // Update status for each service + for (const service of services) { + await c.kv.set('status', service, { + service, + status: 'up', + lastCheck: new Date().toISOString() + }, { ttl: 3600 }); // Cache for 1 hour + } + + c.logger.info('Status update complete', { count: services.length }); + + return c.json({ updated: services.length }); +}); diff --git a/examples/training/03-specialized-routes/step2-email-responder/agent.ts b/examples/training/03-specialized-routes/step2-email-responder/agent.ts new file mode 100644 index 00000000..7321f27b --- /dev/null +++ b/examples/training/03-specialized-routes/step2-email-responder/agent.ts @@ -0,0 +1,30 @@ +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +export const agent = createAgent({ + schema: { + input: z.object({ + from: z.string(), + subject: z.string(), + message: z.string() + }), + output: z.object({ + response: z.string() + }) + }, + metadata: { + name: 'Email Responder', + description: 'Respond to emails' + }, + handler: async (c, input) => { + c.logger.info('Processing email', { + from: input.from, + subject: input.subject + }); + + // Process the email (simple acknowledgment for now) + return { + response: 'Thank you for your email!' + }; + } +}); diff --git a/examples/training/03-specialized-routes/step2-email-responder/route.ts b/examples/training/03-specialized-routes/step2-email-responder/route.ts new file mode 100644 index 00000000..e329b754 --- /dev/null +++ b/examples/training/03-specialized-routes/step2-email-responder/route.ts @@ -0,0 +1,37 @@ +import { createRouter } from '@agentuity/runtime'; + +export const router = createRouter(); + +// EMAIL: Auto-respond to incoming emails +router.email('hello@example.com', async (c) => { + // Parse incoming email + const email = await c.req.email(); + + c.logger.info('Email received', { + from: email.from, + subject: email.subject + }); + + const result = await c.agent.emailResponder.run({ + from: email.from, + subject: email.subject, + message: email.text + }); + + // TODO: Use c.reply() for proper email replies when available + const response = ` +Hello from Agentuity! + +Thank you for your message: + +Subject: ${email.subject} +Message: ${email.text} + +We'll review it and get back to you soon. + +Best regards, +Agentuity + `.trim(); + + return c.text(response); +}); diff --git a/examples/training/03-specialized-routes/step3-realtime-echo/agent.ts b/examples/training/03-specialized-routes/step3-realtime-echo/agent.ts new file mode 100644 index 00000000..5c5399e9 --- /dev/null +++ b/examples/training/03-specialized-routes/step3-realtime-echo/agent.ts @@ -0,0 +1,24 @@ +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +export const agent = createAgent({ + schema: { + input: z.object({ + message: z.string() + }), + output: z.object({ + echo: z.string(), + timestamp: z.string() + }) + }, + metadata: { + name: 'Echo Agent', + description: 'Echo messages back' + }, + handler: async (c, input) => { + return { + echo: input.message, + timestamp: new Date().toISOString() + }; + } +}); diff --git a/examples/training/03-specialized-routes/step3-realtime-echo/route.ts b/examples/training/03-specialized-routes/step3-realtime-echo/route.ts new file mode 100644 index 00000000..25bb494f --- /dev/null +++ b/examples/training/03-specialized-routes/step3-realtime-echo/route.ts @@ -0,0 +1,54 @@ +import { createRouter } from '@agentuity/runtime'; + +export const router = createRouter(); + +// WEBSOCKET: Bidirectional echo +router.websocket('/echo', { + onOpen: (ws, c) => { + c.logger.info('WebSocket connection opened'); + ws.send(JSON.stringify({ + message: 'Connected! Send a message.' + })); + }, + + onMessage: async (ws, message, c) => { + const text = message.toString(); + + // Echo the message back through the agent + const result = await c.agent.echoAgent.run({ message: text }); + + ws.send(JSON.stringify(result)); + }, + + onClose: (ws, c) => { + c.logger.info('WebSocket connection closed'); + } +}); + +// SSE: Server-to-client updates +router.sse('/updates', async (c) => { + c.logger.info('SSE stream started'); + + return c.stream(async (stream) => { + // Send initial message + await stream.send(JSON.stringify({ + message: 'Stream started' + })); + + // Send periodic updates every 5 seconds + let count = 0; + const interval = setInterval(async () => { + count++; + await stream.send(JSON.stringify({ + message: `Update #${count}`, + timestamp: new Date().toISOString() + })); + }, 5000); + + // Cleanup when client disconnects + stream.onClose(() => { + clearInterval(interval); + c.logger.info('SSE stream closed'); + }); + }); +}); diff --git a/examples/training/03-specialized-routes/step4-multi-trigger-notifications/agent.ts b/examples/training/03-specialized-routes/step4-multi-trigger-notifications/agent.ts new file mode 100644 index 00000000..c899e9b7 --- /dev/null +++ b/examples/training/03-specialized-routes/step4-multi-trigger-notifications/agent.ts @@ -0,0 +1,36 @@ +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +export const agent = createAgent({ + schema: { + input: z.object({ + recipient: z.string(), + message: z.string() + }), + output: z.object({ + sent: z.boolean(), + id: z.string() + }) + }, + metadata: { + name: 'Notification Agent', + description: 'Send notifications' + }, + handler: async (c, input) => { + const id = `notif-${Date.now()}`; + + c.logger.info('Sending notification', { + recipient: input.recipient, + id + }); + + // Store notification + await c.kv.set('notifications', id, { + recipient: input.recipient, + message: input.message, + sentAt: new Date().toISOString() + }, { ttl: 86400 }); // 24 hours + + return { sent: true, id }; + } +}); diff --git a/examples/training/03-specialized-routes/step4-multi-trigger-notifications/route.ts b/examples/training/03-specialized-routes/step4-multi-trigger-notifications/route.ts new file mode 100644 index 00000000..21fd369d --- /dev/null +++ b/examples/training/03-specialized-routes/step4-multi-trigger-notifications/route.ts @@ -0,0 +1,48 @@ +import { createRouter } from '@agentuity/runtime'; + +export const router = createRouter(); + +/* Different routes for different triggers */ + +// HTTP: Send notification on-demand +router.post('/notify', async (c) => { + const body = await c.req.json(); + + const result = await c.agent.notificationAgent.run({ + recipient: body.recipient, + message: body.message + }); + + return c.json(result); +}); + +// CRON: Send daily digest at 9 AM +router.cron('0 9 * * *', async (c) => { + c.logger.info('Cron: Sending daily digest'); + + const recipients = ['user1@example.com', 'user2@example.com']; + + // Send to all recipients + for (const recipient of recipients) { + await c.agent.notificationAgent.run({ + recipient, + message: 'Your daily digest is ready' + }); + } + + c.logger.info('Daily digest sent', { count: recipients.length }); + + return c.json({ sent: recipients.length }); +}); + +// EMAIL: Forward email as notification +router.email('notify@example.com', async (c) => { + const email = await c.req.email(); + + await c.agent.notificationAgent.run({ + recipient: email.from, + message: `Email received: ${email.subject}` + }); + + return c.text('Notification sent!'); +}); diff --git a/examples/training/04-state-management/step1-request-state/agent.ts b/examples/training/04-state-management/step1-request-state/agent.ts new file mode 100644 index 00000000..9bb065d6 --- /dev/null +++ b/examples/training/04-state-management/step1-request-state/agent.ts @@ -0,0 +1,51 @@ +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ + task: z.string(), + simulateDelay: z.boolean().optional() + }), + output: z.object({ + result: z.string(), + executionTime: z.number(), + timestamp: z.string() + }) + }, + metadata: { + name: 'Request State Agent', + description: 'Demonstrates ephemeral request-scoped state' + }, + handler: async (c, input) => { + // REQUEST STATE: Set start time for this request only + c.state.set('requestStart', Date.now()); + c.state.set('taskName', input.task); + + // Simulate some processing + if (input.simulateDelay) { + await new Promise(resolve => setTimeout(resolve, 100)); + } + + // Calculate execution time using request state + const startTime = c.state.get('requestStart') as number; + const executionTime = Date.now() - startTime; + + // Log with request-specific context + c.logger.info('Task completed', { + task: input.task, + executionTime, + sessionId: c.sessionId + }); + + return { + result: `Completed: ${input.task}`, + executionTime, + timestamp: new Date().toISOString() + }; + + // REQUEST STATE is automatically cleared after this return + } +}); + +export default agent; diff --git a/examples/training/04-state-management/step1-request-state/route.ts b/examples/training/04-state-management/step1-request-state/route.ts new file mode 100644 index 00000000..680d94ea --- /dev/null +++ b/examples/training/04-state-management/step1-request-state/route.ts @@ -0,0 +1,28 @@ +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +// POST endpoint to process tasks with timing metrics +router.post('/process', async (c) => { + const data = await c.req.json(); + + // Call the agent + const result = await c.agent.requestStateAgent.run(data); + + return c.json(result); +}); + +// GET endpoint to demonstrate request state isolation +router.get('/status', async (c) => { + const result = await c.agent.requestStateAgent.run({ + task: 'status-check', + simulateDelay: false + }); + + return c.json({ + status: 'healthy', + ...result + }); +}); + +export default router; diff --git a/examples/training/04-state-management/step2-thread-state/agent.ts b/examples/training/04-state-management/step2-thread-state/agent.ts new file mode 100644 index 00000000..8aa12e4e --- /dev/null +++ b/examples/training/04-state-management/step2-thread-state/agent.ts @@ -0,0 +1,56 @@ +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ + message: z.string() + }), + output: z.object({ + response: z.string(), + messageCount: z.number(), + threadId: z.string(), + conversationHistory: z.array(z.string()) + }) + }, + metadata: { + name: 'Thread State Agent', + description: 'Demonstrates conversation context with thread state' + }, + handler: async (c, input) => { + // THREAD STATE: Get or initialize conversation history + // Thread state persists across requests for up to 1 hour + const messages = (c.thread.state.get('messages') as string[]) || []; + + // Add new message to conversation + messages.push(input.message); + + // Save updated conversation in thread state + c.thread.state.set('messages', messages); + + // Track when this thread started + if (!c.thread.state.has('threadStartTime')) { + c.thread.state.set('threadStartTime', Date.now()); + } + + const startTime = c.thread.state.get('threadStartTime') as number; + const conversationDuration = Date.now() - startTime; + + c.logger.info('Message added to thread', { + threadId: c.thread.id, + messageCount: messages.length, + conversationDuration + }); + + return { + response: `Message ${messages.length} received`, + messageCount: messages.length, + threadId: c.thread.id, + conversationHistory: messages + }; + + // THREAD STATE persists for up to 1 hour across requests + } +}); + +export default agent; diff --git a/examples/training/04-state-management/step2-thread-state/route.ts b/examples/training/04-state-management/step2-thread-state/route.ts new file mode 100644 index 00000000..c8675cdd --- /dev/null +++ b/examples/training/04-state-management/step2-thread-state/route.ts @@ -0,0 +1,39 @@ +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +// POST endpoint for chat messages +router.post('/chat', async (c) => { + const data = await c.req.json(); + + // Check for reset request + if (data.reset) { + // Manually destroy thread to start fresh conversation + await c.thread.destroy(); + return c.json({ + message: 'Conversation reset. Send a new message to start.', + threadId: null + }); + } + + // Call the agent + const result = await c.agent.threadStateAgent.run({ + message: data.message + }); + + return c.json(result); +}); + +// GET endpoint to view current conversation state +router.get('/chat/history', async (c) => { + // Get conversation history from current thread + const messages = (c.thread.state.get('messages') as string[]) || []; + + return c.json({ + threadId: c.thread.id, + messageCount: messages.length, + messages + }); +}); + +export default router; diff --git a/examples/training/04-state-management/step3-session-state/agent.ts b/examples/training/04-state-management/step3-session-state/agent.ts new file mode 100644 index 00000000..a48a3a2f --- /dev/null +++ b/examples/training/04-state-management/step3-session-state/agent.ts @@ -0,0 +1,74 @@ +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const PreferencesSchema = z.object({ + name: z.string(), + theme: z.enum(['light', 'dark']).default('light'), + language: z.enum(['en', 'es', 'fr']).default('en') +}); + +const agent = createAgent({ + schema: { + input: z.object({ + userId: z.string(), + updatePreferences: PreferencesSchema.partial().optional() + }), + output: z.object({ + preferences: PreferencesSchema, + updateCount: z.number(), + message: z.string() + }) + }, + metadata: { + name: 'Session State Agent', + description: 'Demonstrates user preferences with session state' + }, + handler: async (c, input) => { + const userId = input.userId; + const prefsKey = `preferences_${userId}`; + + // SESSION STATE: Get user preferences (persists across threads) + let preferences = c.session.state.get(prefsKey) as z.infer | undefined; + + // Initialize with defaults if first time + if (!preferences) { + preferences = { + name: 'Guest', + theme: 'light', + language: 'en' + }; + c.session.state.set(prefsKey, preferences); + c.logger.info('New user preferences initialized', { userId }); + } + + // Track update count across all sessions + const updateCountKey = `updateCount_${userId}`; + let updateCount = (c.session.state.get(updateCountKey) as number) || 0; + + // Update preferences if provided + if (input.updatePreferences) { + preferences = { ...preferences, ...input.updatePreferences }; + c.session.state.set(prefsKey, preferences); + updateCount += 1; + c.session.state.set(updateCountKey, updateCount); + + c.logger.info('User preferences updated', { + userId, + updateCount, + changes: input.updatePreferences + }); + } + + return { + preferences, + updateCount, + message: input.updatePreferences + ? 'Preferences updated successfully' + : 'Current preferences retrieved' + }; + + // SESSION STATE persists across thread resets + } +}); + +export default agent; diff --git a/examples/training/04-state-management/step3-session-state/route.ts b/examples/training/04-state-management/step3-session-state/route.ts new file mode 100644 index 00000000..5f770a5c --- /dev/null +++ b/examples/training/04-state-management/step3-session-state/route.ts @@ -0,0 +1,42 @@ +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +// GET user preferences +router.get('/users/:userId/preferences', async (c) => { + const userId = c.req.param('userId'); + + const result = await c.agent.sessionStateAgent.run({ + userId + }); + + return c.json(result); +}); + +// POST to update user preferences +router.post('/users/:userId/preferences', async (c) => { + const userId = c.req.param('userId'); + const updatePreferences = await c.req.json(); + + const result = await c.agent.sessionStateAgent.run({ + userId, + updatePreferences + }); + + return c.json(result); +}); + +// DELETE to reset user preferences (for testing) +router.delete('/users/:userId/preferences', async (c) => { + const userId = c.req.param('userId'); + + // Clear session state for this user + c.session.state.delete(`preferences_${userId}`); + c.session.state.delete(`updateCount_${userId}`); + + return c.json({ + message: `Preferences cleared for user ${userId}` + }); +}); + +export default router; diff --git a/examples/training/04-state-management/step4-state-lifecycle/agent.ts b/examples/training/04-state-management/step4-state-lifecycle/agent.ts new file mode 100644 index 00000000..ad570ae9 --- /dev/null +++ b/examples/training/04-state-management/step4-state-lifecycle/agent.ts @@ -0,0 +1,67 @@ +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ + message: z.string() + }), + output: z.object({ + response: z.string(), + messageCount: z.number(), + threadId: z.string(), + conversationDuration: z.number() + }) + }, + metadata: { + name: 'State Lifecycle Agent', + description: 'Demonstrates thread lifecycle, cleanup, and event handling' + }, + handler: async (c, input) => { + // Register cleanup handler once per thread + if (!c.thread.state.has('cleanupRegistered')) { + c.thread.addEventListener('destroyed', async (eventName, thread) => { + const messages = thread.state.get('messages') as string[] || []; + const startTime = thread.state.get('startTime') as number || Date.now(); + const duration = Date.now() - startTime; + + c.logger.info('Thread destroyed - cleanup executing', { + threadId: thread.id, + messageCount: messages.length, + conversationDuration: duration, + reason: 'Expiration or manual destroy' + }); + + // In a real app, you might save conversation summary to KV storage here + // await c.kv.set('conversation-summaries', thread.id, { ... }); + + // Clear thread state + thread.state.clear(); + }); + + c.thread.state.set('cleanupRegistered', true); + c.thread.state.set('startTime', Date.now()); + + c.logger.info('Thread lifecycle tracking initialized', { + threadId: c.thread.id + }); + } + + // Track messages in thread state + const messages = (c.thread.state.get('messages') as string[]) || []; + messages.push(input.message); + c.thread.state.set('messages', messages); + + const startTime = c.thread.state.get('startTime') as number; + const conversationDuration = Date.now() - startTime; + + return { + response: `Message ${messages.length} stored. Thread expires in 1 hour.`, + messageCount: messages.length, + threadId: c.thread.id, + conversationDuration + }; + } +}); + +export default agent; diff --git a/examples/training/04-state-management/step4-state-lifecycle/route.ts b/examples/training/04-state-management/step4-state-lifecycle/route.ts new file mode 100644 index 00000000..16fa48f8 --- /dev/null +++ b/examples/training/04-state-management/step4-state-lifecycle/route.ts @@ -0,0 +1,41 @@ +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +// POST endpoint for messages +router.post('/conversation', async (c) => { + const data = await c.req.json(); + + const result = await c.agent.lifecycleAgent.run({ + message: data.message + }); + + return c.json(result); +}); + +// POST endpoint to manually reset conversation +router.post('/conversation/reset', async (c) => { + // Manually destroy thread - triggers 'destroyed' event + await c.thread.destroy(); + + return c.json({ + message: 'Conversation reset. Thread destroyed and cleanup executed.', + info: 'Check logs to see cleanup event handler execution' + }); +}); + +// GET endpoint to view thread status +router.get('/conversation/status', async (c) => { + const messages = (c.thread.state.get('messages') as string[]) || []; + const startTime = c.thread.state.get('startTime') as number; + const duration = startTime ? Date.now() - startTime : 0; + + return c.json({ + threadId: c.thread.id, + messageCount: messages.length, + conversationDuration: duration, + expiresIn: '1 hour from thread creation' + }); +}); + +export default router; diff --git a/examples/training/05-storage-apis/step1-kv-basics/agent.ts b/examples/training/05-storage-apis/step1-kv-basics/agent.ts new file mode 100644 index 00000000..7b1b7ed3 --- /dev/null +++ b/examples/training/05-storage-apis/step1-kv-basics/agent.ts @@ -0,0 +1,68 @@ +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ + query: z.string() + }), + output: z.object({ + result: z.string(), + cached: z.boolean(), + cacheKey: z.string() + }) + }, + metadata: { + name: 'KV Basics Agent', + description: 'Demonstrates KV storage with TTL for caching' + }, + handler: async (c, input) => { + const bucket = 'api-cache'; + const cacheKey = `query:${input.query.toLowerCase().replace(/\s+/g, '-')}`; + + // Check cache first + const cached = await c.kv.get(bucket, cacheKey); + + if (cached.exists) { + // Cache hit - return immediately + const data = cached.data as string; + + c.logger.info('Cache hit', { + cacheKey, + query: input.query + }); + + return { + result: data, + cached: true, + cacheKey + }; + } + + // Cache miss - simulate expensive computation + c.logger.info('Cache miss - computing result', { + cacheKey, + query: input.query + }); + + await new Promise(resolve => setTimeout(resolve, 100)); + const result = `Computed result for: ${input.query}`; + + // Store in cache with 5-minute TTL + await c.kv.set(bucket, cacheKey, result, { + ttl: 300 // 5 minutes in seconds + }); + + c.logger.info('Result cached with 5-minute TTL', { + cacheKey + }); + + return { + result, + cached: false, + cacheKey + }; + } +}); + +export default agent; diff --git a/examples/training/05-storage-apis/step1-kv-basics/route.ts b/examples/training/05-storage-apis/step1-kv-basics/route.ts new file mode 100644 index 00000000..eca84242 --- /dev/null +++ b/examples/training/05-storage-apis/step1-kv-basics/route.ts @@ -0,0 +1,28 @@ +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +// POST endpoint for cached queries +router.post('/query', async (c) => { + const data = await c.req.json(); + + const result = await c.agent.kvBasicsAgent.run({ + query: data.query + }); + + return c.json(result); +}); + +// DELETE endpoint to clear cache for testing +router.delete('/cache/:key', async (c) => { + const key = c.req.param('key'); + const bucket = 'api-cache'; + + await c.kv.delete(bucket, key); + + return c.json({ + message: `Cache cleared for key: ${key}` + }); +}); + +export default router; diff --git a/examples/training/05-storage-apis/step2-kv-persistent/agent.ts b/examples/training/05-storage-apis/step2-kv-persistent/agent.ts new file mode 100644 index 00000000..cb878163 --- /dev/null +++ b/examples/training/05-storage-apis/step2-kv-persistent/agent.ts @@ -0,0 +1,84 @@ +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const PreferencesSchema = z.object({ + theme: z.enum(['light', 'dark']), + language: z.string(), + notifications: z.boolean() +}); + +const agent = createAgent({ + schema: { + input: z.object({ + userId: z.string(), + updatePreferences: PreferencesSchema.partial().optional() + }), + output: z.object({ + preferences: PreferencesSchema, + source: z.enum(['existing', 'default']), + updated: z.boolean() + }) + }, + metadata: { + name: 'KV Persistent Agent', + description: 'Demonstrates persistent KV storage without TTL' + }, + handler: async (c, input) => { + const bucket = 'user-preferences'; + const key = `prefs:${input.userId}`; + + // Load existing preferences (no TTL - persists indefinitely) + const result = await c.kv.get(bucket, key); + + let preferences: z.infer; + let source: 'existing' | 'default' = 'default'; + let updated = false; + + if (result.exists) { + // User has existing preferences + preferences = result.data as z.infer; + source = 'existing'; + + c.logger.info('Loaded existing preferences', { + userId: input.userId + }); + } else { + // New user - initialize with defaults + preferences = { + theme: 'light', + language: 'en', + notifications: true + }; + + c.logger.info('Initialized default preferences', { + userId: input.userId + }); + } + + // Update preferences if provided + if (input.updatePreferences) { + preferences = { + ...preferences, + ...input.updatePreferences + }; + + // Save to KV storage WITHOUT TTL (persists indefinitely) + await c.kv.set(bucket, key, preferences); + + updated = true; + + c.logger.info('Updated user preferences', { + userId: input.userId, + changes: input.updatePreferences + }); + } + + return { + preferences, + source, + updated + }; + } +}); + +export default agent; diff --git a/examples/training/05-storage-apis/step2-kv-persistent/route.ts b/examples/training/05-storage-apis/step2-kv-persistent/route.ts new file mode 100644 index 00000000..8eb41ebd --- /dev/null +++ b/examples/training/05-storage-apis/step2-kv-persistent/route.ts @@ -0,0 +1,29 @@ +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +// GET user preferences +router.get('/users/:userId/settings', async (c) => { + const userId = c.req.param('userId'); + + const result = await c.agent.kvPersistentAgent.run({ + userId + }); + + return c.json(result); +}); + +// POST to update user preferences +router.post('/users/:userId/settings', async (c) => { + const userId = c.req.param('userId'); + const updatePreferences = await c.req.json(); + + const result = await c.agent.kvPersistentAgent.run({ + userId, + updatePreferences + }); + + return c.json(result); +}); + +export default router; diff --git a/examples/training/05-storage-apis/step3-vector-basics/agent.ts b/examples/training/05-storage-apis/step3-vector-basics/agent.ts new file mode 100644 index 00000000..914f476d --- /dev/null +++ b/examples/training/05-storage-apis/step3-vector-basics/agent.ts @@ -0,0 +1,94 @@ +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ + query: z.string() + }), + output: z.object({ + query: z.string(), + resultsFound: z.number(), + results: z.array(z.object({ + key: z.string(), + similarity: z.number(), + category: z.string().optional() + })) + }) + }, + metadata: { + name: 'Vector Basics Agent', + description: 'Demonstrates vector storage with variadic upsert pattern' + }, + handler: async (c, input) => { + const bucket = 'knowledge-base'; + + // Sample knowledge base entries + const facts = [ + { + key: 'fact-1', + document: 'Agentuity is an agent-native cloud platform designed for AI agents', + metadata: { category: 'platform' } + }, + { + key: 'fact-2', + document: 'Vector storage enables semantic search by meaning, not keywords', + metadata: { category: 'features' } + }, + { + key: 'fact-3', + document: 'Key-value storage is perfect for caching and session state', + metadata: { category: 'features' } + } + ]; + + // VARIADIC UPSERT PATTERN: Pass multiple documents at once + // Option 1: Spread array (recommended for dynamic lists) + await c.vector.upsert(bucket, ...facts); + + // Option 2: Individual parameters (good for static data) + // await c.vector.upsert( + // bucket, + // { key: 'fact-1', document: '...', metadata: {...} }, + // { key: 'fact-2', document: '...', metadata: {...} }, + // { key: 'fact-3', document: '...', metadata: {...} } + // ); + + c.logger.info('Upserted documents to vector storage', { + bucket, + count: facts.length + }); + + // Search for semantically similar documents + const searchResults = await c.vector.search(bucket, { + query: input.query, + limit: 3, + similarity: 0.5 + }); + + c.logger.info('Vector search completed', { + query: input.query, + resultsFound: searchResults.length + }); + + // Format results + const results = searchResults.map(result => ({ + key: result.key, + similarity: result.similarity, + category: result.metadata?.category as string | undefined + })); + + // Cleanup demo data + for (const fact of facts) { + await c.vector.delete(bucket, fact.key); + } + + return { + query: input.query, + resultsFound: searchResults.length, + results + }; + } +}); + +export default agent; diff --git a/examples/training/05-storage-apis/step3-vector-basics/route.ts b/examples/training/05-storage-apis/step3-vector-basics/route.ts new file mode 100644 index 00000000..f27a470a --- /dev/null +++ b/examples/training/05-storage-apis/step3-vector-basics/route.ts @@ -0,0 +1,16 @@ +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +// POST endpoint for semantic search +router.post('/search', async (c) => { + const data = await c.req.json(); + + const result = await c.agent.vectorBasicsAgent.run({ + query: data.query + }); + + return c.json(result); +}); + +export default router; diff --git a/examples/training/05-storage-apis/step4-vector-filtering/agent.ts b/examples/training/05-storage-apis/step4-vector-filtering/agent.ts new file mode 100644 index 00000000..1d6608d7 --- /dev/null +++ b/examples/training/05-storage-apis/step4-vector-filtering/agent.ts @@ -0,0 +1,115 @@ +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ + query: z.string(), + category: z.string().optional(), + inStock: z.boolean().optional() + }), + output: z.object({ + query: z.string(), + filters: z.object({ + category: z.string().optional(), + inStock: z.boolean().optional() + }), + resultsFound: z.number(), + results: z.array(z.object({ + key: z.string(), + similarity: z.number(), + category: z.string(), + price: z.number(), + inStock: z.boolean() + })) + }) + }, + metadata: { + name: 'Vector Filtering Agent', + description: 'Demonstrates vector search with metadata filtering' + }, + handler: async (c, input) => { + const bucket = 'product-catalog'; + + // Sample product catalog + const products = [ + { + key: 'chair-001', + document: 'Ergonomic office chair with lumbar support', + metadata: { category: 'furniture', price: 299, inStock: true } + }, + { + key: 'desk-001', + document: 'Standing desk with adjustable height', + metadata: { category: 'furniture', price: 599, inStock: true } + }, + { + key: 'laptop-001', + document: 'High-performance laptop for development', + metadata: { category: 'electronics', price: 1499, inStock: false } + }, + { + key: 'monitor-001', + document: '4K monitor with USB-C connectivity', + metadata: { category: 'electronics', price: 499, inStock: true } + } + ]; + + // Upsert products using variadic pattern + await c.vector.upsert(bucket, ...products); + + c.logger.info('Upserted products', { + bucket, + count: products.length + }); + + // Build metadata filters + const metadataFilter: Record = {}; + if (input.category) { + metadataFilter.category = input.category; + } + if (input.inStock !== undefined) { + metadataFilter.inStock = input.inStock; + } + + // Search with metadata filtering + const searchResults = await c.vector.search(bucket, { + query: input.query, + limit: 10, + similarity: 0.3, // Lower threshold when using filters + ...(Object.keys(metadataFilter).length > 0 && { metadata: metadataFilter }) + }); + + c.logger.info('Vector search with filters completed', { + query: input.query, + filters: metadataFilter, + resultsFound: searchResults.length + }); + + // Format results + const results = searchResults.map(result => ({ + key: result.key, + similarity: result.similarity, + category: result.metadata?.category as string, + price: result.metadata?.price as number, + inStock: result.metadata?.inStock as boolean + })); + + // Cleanup demo data + for (const product of products) { + await c.vector.delete(bucket, product.key); + } + + return { + query: input.query, + filters: { + category: input.category, + inStock: input.inStock + }, + resultsFound: searchResults.length, + results + }; + } +}); + +export default agent; diff --git a/examples/training/05-storage-apis/step4-vector-filtering/route.ts b/examples/training/05-storage-apis/step4-vector-filtering/route.ts new file mode 100644 index 00000000..8f6704d6 --- /dev/null +++ b/examples/training/05-storage-apis/step4-vector-filtering/route.ts @@ -0,0 +1,18 @@ +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +// POST endpoint for filtered semantic search +router.post('/products/search', async (c) => { + const data = await c.req.json(); + + const result = await c.agent.vectorFilteringAgent.run({ + query: data.query, + category: data.category, + inStock: data.inStock + }); + + return c.json(result); +}); + +export default router; diff --git a/examples/training/05-storage-apis/step5-object-storage/agent.ts b/examples/training/05-storage-apis/step5-object-storage/agent.ts new file mode 100644 index 00000000..3062552c --- /dev/null +++ b/examples/training/05-storage-apis/step5-object-storage/agent.ts @@ -0,0 +1,77 @@ +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ + content: z.string(), + filename: z.string().optional() + }), + output: z.object({ + message: z.string(), + filename: z.string(), + contentLength: z.number(), + publicUrl: z.string(), + urlExpiresIn: z.string() + }) + }, + metadata: { + name: 'Object Storage Agent', + description: 'Demonstrates object storage for files and binary data' + }, + handler: async (c, input) => { + const bucket = 'demo-files'; + const filename = input.filename || `file-${Date.now()}.txt`; + + // Store content as text file + await c.objectstore.put(bucket, filename, input.content); + + c.logger.info('File stored in object storage', { + bucket, + filename, + size: input.content.length + }); + + // Retrieve the stored file + const result = await c.objectstore.get(bucket, filename); + + if (!result.exists) { + throw new Error('File not found after upload'); + } + + const retrievedContent = await result.data.text(); + + c.logger.info('File retrieved from object storage', { + filename, + contentLength: retrievedContent.length + }); + + // Create temporary public URL (expires in 1 hour) + const publicUrl = await c.objectstore.createPublicURL( + bucket, + filename, + 3600000 // 1 hour in milliseconds + ); + + c.logger.info('Created public URL', { + filename, + url: publicUrl, + expiresIn: '1 hour' + }); + + // Cleanup - delete demo file + await c.objectstore.delete(bucket, filename); + + c.logger.info('Cleaned up demo file', { filename }); + + return { + message: 'File stored, retrieved, and public URL created', + filename, + contentLength: retrievedContent.length, + publicUrl, + urlExpiresIn: '1 hour' + }; + } +}); + +export default agent; diff --git a/examples/training/05-storage-apis/step5-object-storage/route.ts b/examples/training/05-storage-apis/step5-object-storage/route.ts new file mode 100644 index 00000000..2f5871d7 --- /dev/null +++ b/examples/training/05-storage-apis/step5-object-storage/route.ts @@ -0,0 +1,17 @@ +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +// POST endpoint to upload and manage files +router.post('/files/upload', async (c) => { + const data = await c.req.json(); + + const result = await c.agent.objectStorageAgent.run({ + content: data.content, + filename: data.filename + }); + + return c.json(result); +}); + +export default router; From ebd47b57bac29dc8f4a9f894121dc574b62d3bf3 Mon Sep 17 00:00:00 2001 From: parteeksingh24 Date: Wed, 19 Nov 2025 14:49:06 -0800 Subject: [PATCH 04/63] Add some marketing terms --- content/v1/Guides/evaluations.mdx | 9 +- content/v1/Guides/routing-triggers.mdx | 1 + content/v1/Introduction/architecture.mdx | 130 +++++++++++++++++- content/v1/Introduction/core-concepts.mdx | 11 +- content/v1/Introduction/introduction.mdx | 25 ++-- .../v1/Training/01-intro-to-agents/index.mdx | 6 +- 6 files changed, 160 insertions(+), 22 deletions(-) diff --git a/content/v1/Guides/evaluations.mdx b/content/v1/Guides/evaluations.mdx index 713ff026..379c0f88 100644 --- a/content/v1/Guides/evaluations.mdx +++ b/content/v1/Guides/evaluations.mdx @@ -20,10 +20,11 @@ Evaluations (evals) are automated tests that run after your agent completes. The - **Independent**: Multiple evals can run on a single agent; errors in one don't affect others **Use cases:** -- Quality checking (accuracy, relevance, completeness) -- Compliance validation (PII detection, content policy) -- Performance monitoring (response time, token usage) -- A/B testing and regression testing +- Quality checking (accuracy, relevance, completeness of your agent's outputs) +- Compliance validation (PII detection, content policy enforcement) +- Performance monitoring (response time, resource usage across your workflow) +- RAG system quality (hallucination detection, faithfulness, contextual relevancy) +- A/B testing and regression testing of agent behavior ## Creating Evaluations diff --git a/content/v1/Guides/routing-triggers.mdx b/content/v1/Guides/routing-triggers.mdx index 24599da0..3d59cdef 100644 --- a/content/v1/Guides/routing-triggers.mdx +++ b/content/v1/Guides/routing-triggers.mdx @@ -13,6 +13,7 @@ All triggers are defined in your codebase (`route.ts` files) and are automatical - **Code review** - Changes to triggers are visible in pull requests - **Testability** - Routes can be unit tested like any other code - **UI visibility** - Configured routes are displayed in the Agentuity dashboard for monitoring +- **Built-in infrastructure** - Routes automatically include load balancing, monitoring, and error tracking No manual UI configuration required. Define routes directly in your agent code, deploy, and they're immediately available.
diff --git a/content/v1/Introduction/architecture.mdx b/content/v1/Introduction/architecture.mdx index f8ced639..47756046 100644 --- a/content/v1/Introduction/architecture.mdx +++ b/content/v1/Introduction/architecture.mdx @@ -53,6 +53,67 @@ Agentuity consists of five primary components: - Usage analytics, logging, monitoring and performance insights - Team collaboration features +## Built-in Services + +Agentuity includes built-in services that work out of the box. These services are accessible through the agent context (`c`) and require minimal configuration. + +### Routing and Connectivity + +| Service | Description | Access | +|---------|-------------|--------| +| HTTP/WebSocket/SSE | Built on Hono framework with automatic load balancing | `router.get()`, `router.post()`, `router.websocket()`, `router.sse()` | +| Email Handling | Define email addresses as routes to trigger agents from incoming messages | `router.email()` | +| SMS Integration | Route SMS messages to agents based on phone numbers | `router.sms()` | +| Cron Scheduling | Schedule periodic agent execution with cron expressions | `router.cron()` | +| Authentication | Configurable authentication and rate limiting per route | Middleware | + +### Storage Layer + +| Service | Description | Access | +|---------|-------------|--------| +| Key-Value Storage | Fast key-value store with TTL support | `c.kv` | +| Vector Database | Embeddings storage and semantic search | `c.vector` | +| Object Storage | Blob and file storage with public URL generation | `c.objectstore` | +| Stream Storage | Large data streaming and processing | `c.stream` | + +### Observability + +| Service | Description | Access | +|---------|-------------|--------| +| Structured Logging | Built-in logger with contextual information | `c.logger` | +| OpenTelemetry Tracing | Distributed tracing for agent interactions | `c.tracer` | +| Real-time Analytics | Execution metrics, performance data, and usage tracking | Web Console | +| Error Tracking | Automatic error capture and reporting | Web Console | + +### AI Gateway + +| Feature | Description | +|---------|-------------| +| Unified LLM Access | Single interface for multiple LLM providers (OpenAI, Anthropic, etc.) | +| Managed API Keys | Centralized credential management for LLM services | +| Model Switching | Change providers without code modifications | +| Usage Tracking | Monitor LLM API usage and costs | + +### Evaluation Framework + +| Feature | Description | +|---------|-------------| +| Real-time Testing | Test agent input/output during execution using `agent.createEval()` | +| Quality Assurance | Automated checks for accuracy, relevance, and compliance | +| Custom Evaluators | Define custom evaluation logic for specific requirements | +| Non-blocking Execution | Evaluations run asynchronously without impacting response times | + +### Frontend Deployment + +| Feature | Description | +|---------|-------------| +| React Integration | Deploy React applications alongside agents | +| Hot Reload | Automatic reload during development | +| CDN Distribution | Built-in CDN for global frontend delivery | +| Custom Domains | Configure custom domains with automatic SSL certificates | + +All services are configured in `agentuity.yaml` and accessed through the agent context. No additional infrastructure setup required. + ## SDK Architecture Agentuity v1 uses a monorepo structure with specialized packages for different concerns: @@ -143,6 +204,65 @@ Security is foundational to Agentuity's design: - **Encrypted Communications** - All agent-to-agent communication is encrypted - **Secure Deployment** - Protected deployment pipeline from development to production +## Deployment Targets + +Agentuity supports multiple deployment targets, allowing you to choose where your agents run based on your requirements. The same agent code and configuration work across all targets. + +### Public Cloud + +Deploy to Agentuity's managed infrastructure for immediate scalability: + +- **Global Edge Network** - Agents run on a distributed edge network for low-latency responses +- **Automatic Scaling** - Scale from zero to thousands of requests without configuration +- **Fast Cold Starts** - Sub-100ms cold start times on the global edge +- **Managed Services** - All infrastructure, monitoring, and maintenance handled by Agentuity + +Best for: Rapid deployment, global distribution, and zero infrastructure management. + +### Private Cloud + +Deploy agents to your own Virtual Private Cloud (VPC) while using Agentuity services: + +- **Infrastructure Control** - Agents run in your AWS, GCP, or Azure account +- **Data Security** - Your data never leaves your infrastructure +- **Agentuity Services** - Access storage, observability, and platform features +- **Compliance** - Meet regulatory requirements for data residency + +Best for: Organizations with strict data governance or compliance requirements. + +### Multi-Cloud + +Deploy agents across multiple cloud providers with consistent configuration: + +- **Provider Flexibility** - Run on AWS, GCP, Azure, or combinations +- **Avoid Lock-in** - Move between providers without code changes +- **Geographic Distribution** - Place agents near users across different clouds +- **Consistent Tooling** - Same CLI, SDK, and deployment process everywhere + +Best for: Organizations requiring cloud provider flexibility or multi-region deployments. + +### On-Premises + +Self-host Agentuity infrastructure on your own hardware: + +- **Complete Control** - Full ownership of infrastructure and data +- **Air-Gapped Support** - Run in isolated networks without internet access +- **Custom Hardware** - Deploy on specialized hardware or existing infrastructure +- **Data Sovereignty** - Guarantee data never leaves your physical premises + +Best for: Highly regulated industries, air-gapped environments, or specific hardware requirements. + +### Edge Deployment + +Deploy agents to edge devices for local processing: + +- **Local Processing** - Run agents on Raspberry Pi, laptops, or IoT devices +- **Low Latency** - Process data at the edge without network round-trips +- **Offline Operation** - Agents function without cloud connectivity +- **Resource Efficiency** - Optimized for resource-constrained environments + +Best for: IoT applications, offline scenarios, or latency-sensitive workloads. + ## Project Conventions Agentuity projects follow specific conventions to take advantage of the deployment and cloud platform Agentuity offers. Understanding these conventions is important for effective agent development. @@ -417,10 +537,10 @@ const agent = createAgent({ These conventions enable several key capabilities: -1. **Consistent Development Experience**: Standardized structure makes it easier to work across projects -2. **Automated Deployment**: The CLI can package and deploy your project without additional configuration -3. **Framework Flexibility**: Use any agent framework while maintaining compatibility with the platform -4. **Type Safety**: Full TypeScript support throughout the stack -5. **Scalability**: Clear separation of concerns makes it easy to organize complex agent systems +1. **Consistent Development Experience** - Standardized structure makes it easier to work across projects +2. **Automated Deployment** - The CLI can package and deploy your project without additional configuration +3. **Framework Flexibility** - Use any agent framework while maintaining compatibility with the platform +4. **Type Safety** - Full TypeScript support throughout the stack +5. **Scalability** - Clear separation of concerns makes it easy to organize complex agent systems TODO: Add examples showing integration with frontend frameworks (Next.js, Svelte, vanilla JS, etc.) for building UIs that call agents diff --git a/content/v1/Introduction/core-concepts.mdx b/content/v1/Introduction/core-concepts.mdx index 5c37b939..62669444 100644 --- a/content/v1/Introduction/core-concepts.mdx +++ b/content/v1/Introduction/core-concepts.mdx @@ -105,6 +105,8 @@ router.post('/', async (c) => { export default router; ``` +Routes automatically include load balancing, monitoring, and error tracking. You can configure authentication and rate limiting per route using middleware. + **Advanced Routing:** v1 supports WebSocket, Server-Sent Events, email, cron, and more. See the [Routing & Triggers Guide](/Guides/routing-triggers) for details. @@ -277,7 +279,7 @@ agent.addEventListener('completed', (eventName, agent, ctx) => { See the [Events Guide](/SDKs/javascript/events) for complete documentation. -**Evaluations** automatically test agent outputs for quality, accuracy, or compliance: +**Evaluations** automatically test agent outputs for quality, accuracy, or compliance. Evaluations test your complete agent logic—the actual input and output from your handler—not just external LLM API responses. ```typescript agent.createEval({ @@ -295,12 +297,19 @@ agent.createEval({ }); ``` +Evaluations run asynchronously after responses are sent, ensuring zero impact on response times. Common use cases: + +- Quality assurance and compliance checking +- RAG metrics (hallucination detection, faithfulness) +- Performance monitoring and optimization + See the [Evaluations Guide](/SDKs/javascript/evaluations) for complete documentation. ## Next Steps Now that you understand the core concepts, you can: +- TODO: Create dedicated deployment guide - Explore the [Routing & Triggers Guide](/Guides/routing-triggers) for WebSocket, SSE, and specialized routes - Learn about [Schema Validation](/SDKs/javascript/schema-validation) in depth - Set up [Evaluations](/SDKs/javascript/evaluations) to test your agents diff --git a/content/v1/Introduction/introduction.mdx b/content/v1/Introduction/introduction.mdx index d7e42397..d4a115de 100644 --- a/content/v1/Introduction/introduction.mdx +++ b/content/v1/Introduction/introduction.mdx @@ -9,7 +9,10 @@ description: Agentuity is rebuilding the cloud for AI Agents [Agentuity](https://agentuity.com) is a cloud platform designed specifically to make it easy to build, deploy, and operate AI Agents at scale. -Our mission is to provide a fully agentic infrastructure and tools necessary to build Agents that are fully operated by AI. +Our mission is to provide a fully agentic infrastructure and tools necessary to build Agents that are fully operated by AI. That means we allow you to: + +- Build agents like you build web apps: define logic, configure routes, and deploy with a single command. +- Focus on agent behavior, while we handle the infrastructure. If you're ready to dive in, skip to [Getting Started](/Introduction/getting-started) to get your first Agent up and running in minutes. @@ -18,19 +21,23 @@ If you're ready to dive in, skip to [Getting Started](/Introduction/getting-star With Agentuity, you or your agents can: -- Deploy agents with a single command to a fully agentic infrastructure -- Monitor real-time performance, analytics, metrics and logs -- Auto scale agents effortlessly and on-demand -- Connect agents to various input and output channels (HTTP, WebSocket, email, SMS, cron, and more) -- Build type-safe agents with schema validation and automated testing -- Securely communicate between agents and build complex agentic workflows -- Create interactive UIs for your agents with React components -- Use any AI agent framework with Node.js or Bun +- Deploy agents with a single command without configuring IAM roles, load balancers, or security groups +- Connect agents to multiple channels (HTTP, WebSocket, Server-Sent Events, email, SMS, cron, and more) +- Build type-safe agents with schema validation and automated evaluation framework +- Access built-in storage (key-value, vector, object, stream) and observability (logging, tracing, metrics) +- Communicate securely between agents to build multi-agent workflows +- Deploy React frontends alongside agents with hot reload and CDN distribution +- Monitor agent performance with real-time analytics, logs, and OpenTelemetry tracing +- Scale automatically from zero to thousands of requests on-demand

We see a near future where Agents are the primary way to build and operate software and where all the infrastructure is built uniquely for them.

+## Deployment Options + +Deploy anywhere—from Agentuity's global edge network to your own infrastructure. The same code runs on public cloud, private VPC, on-premises, or edge devices. See the [Architecture overview](/Introduction/architecture#deployment-targets) for more details. + ## Agentuity Platform Overview TODO: Add platform overview video diff --git a/content/v1/Training/01-intro-to-agents/index.mdx b/content/v1/Training/01-intro-to-agents/index.mdx index e35644c3..b2de96b3 100644 --- a/content/v1/Training/01-intro-to-agents/index.mdx +++ b/content/v1/Training/01-intro-to-agents/index.mdx @@ -146,9 +146,9 @@ Instead of asking "How do we make Lambda work for agents?", we asked "What would Agentuity provides what agents actually need: - **Long-running processes**: Agents can think for hours, not seconds -- **Persistent memory**: Built-in [key-value](/Guides/key-value), [vector](/Guides/vector-db), and [object storage](/Guides/object-storage) -- **Agent-to-agent communication**: Seamless and secure [channels between agents](/Guides/agent-communication) -- **Native observability**: Track agent decisions with [built-in tracing](/Guides/agent-tracing) +- **Built-in persistent memory**: [key-value](/Guides/key-value), [vector](/Guides/vector-db), and [object storage](/Guides/object-storage) +- **Native agent-to-agent communication**: Seamless and secure [channels between agents](/Guides/agent-communication) +- **Built-in observability**: Track agent decisions with [tracing](/Guides/agent-tracing) and OpenTelemetry integration - **Automatic scaling**: Based on agent workload, not request count - **Framework agnostic**: Run agents on any framework (LangChain, CrewAI, custom, etc.), and combine them in multi-agent projects - **Easily integrate with popular tools**: Including the Vercel AI SDK for streamlined AI development From d2cc92f719c1613242dd6b03eabdc2d07f383158 Mon Sep 17 00:00:00 2001 From: parteeksingh24 Date: Wed, 19 Nov 2025 17:12:05 -0800 Subject: [PATCH 05/63] Add remaining Training docs --- .../Training/06-agent-communication/index.mdx | 265 +++++++++++ .../index.mdx | 335 ++++++++++++++ content/v1/Training/08-deployment/index.mdx | 429 ++++++++++++++++++ .../v1/Training/09-sandbox-capstone/index.mdx | 283 ++++++++++++ .../coordinator/agent.ts | 48 ++ .../coordinator/route.ts | 13 + .../step1-basic-agent-calls/enricher/agent.ts | 47 ++ .../step1-basic-agent-calls/enricher/route.ts | 10 + .../coordinator/agent.ts | 53 +++ .../coordinator/route.ts | 13 + .../aggregator/agent.ts | 56 +++ .../aggregator/route.ts | 13 + .../step4-smart-routing/router/agent.ts | 84 ++++ .../step4-smart-routing/router/route.ts | 13 + .../support-agent/agent.ts | 67 +++ .../support-agent/route.ts | 10 + .../step1-basic-evaluations/agent.ts | 74 +++ .../step1-basic-evaluations/route.ts | 13 + .../step2-advanced-evals/agent.ts | 88 ++++ .../step2-advanced-evals/route.ts | 13 + .../step3-event-system/agent.ts | 73 +++ .../step3-event-system/route.ts | 13 + .../step4-structured-logging/agent.ts | 58 +++ .../step4-structured-logging/route.ts | 13 + .../step5-advanced-validation/agent.ts | 59 +++ .../step5-advanced-validation/route.ts | 13 + .../step6-custom-spans/agent.ts | 93 ++++ .../step6-custom-spans/route.ts | 13 + 28 files changed, 2262 insertions(+) create mode 100644 content/v1/Training/06-agent-communication/index.mdx create mode 100644 content/v1/Training/07-observability-evals-validation/index.mdx create mode 100644 content/v1/Training/08-deployment/index.mdx create mode 100644 content/v1/Training/09-sandbox-capstone/index.mdx create mode 100644 examples/training/06-agent-communication/step1-basic-agent-calls/coordinator/agent.ts create mode 100644 examples/training/06-agent-communication/step1-basic-agent-calls/coordinator/route.ts create mode 100644 examples/training/06-agent-communication/step1-basic-agent-calls/enricher/agent.ts create mode 100644 examples/training/06-agent-communication/step1-basic-agent-calls/enricher/route.ts create mode 100644 examples/training/06-agent-communication/step2-sequential-workflows/coordinator/agent.ts create mode 100644 examples/training/06-agent-communication/step2-sequential-workflows/coordinator/route.ts create mode 100644 examples/training/06-agent-communication/step3-parallel-execution/aggregator/agent.ts create mode 100644 examples/training/06-agent-communication/step3-parallel-execution/aggregator/route.ts create mode 100644 examples/training/06-agent-communication/step4-smart-routing/router/agent.ts create mode 100644 examples/training/06-agent-communication/step4-smart-routing/router/route.ts create mode 100644 examples/training/06-agent-communication/step4-smart-routing/support-agent/agent.ts create mode 100644 examples/training/06-agent-communication/step4-smart-routing/support-agent/route.ts create mode 100644 examples/training/07-observability-evals-validation/step1-basic-evaluations/agent.ts create mode 100644 examples/training/07-observability-evals-validation/step1-basic-evaluations/route.ts create mode 100644 examples/training/07-observability-evals-validation/step2-advanced-evals/agent.ts create mode 100644 examples/training/07-observability-evals-validation/step2-advanced-evals/route.ts create mode 100644 examples/training/07-observability-evals-validation/step3-event-system/agent.ts create mode 100644 examples/training/07-observability-evals-validation/step3-event-system/route.ts create mode 100644 examples/training/07-observability-evals-validation/step4-structured-logging/agent.ts create mode 100644 examples/training/07-observability-evals-validation/step4-structured-logging/route.ts create mode 100644 examples/training/07-observability-evals-validation/step5-advanced-validation/agent.ts create mode 100644 examples/training/07-observability-evals-validation/step5-advanced-validation/route.ts create mode 100644 examples/training/07-observability-evals-validation/step6-custom-spans/agent.ts create mode 100644 examples/training/07-observability-evals-validation/step6-custom-spans/route.ts diff --git a/content/v1/Training/06-agent-communication/index.mdx b/content/v1/Training/06-agent-communication/index.mdx new file mode 100644 index 00000000..6ed9e418 --- /dev/null +++ b/content/v1/Training/06-agent-communication/index.mdx @@ -0,0 +1,265 @@ +--- +title: "Module 6: Agent Communication" +description: Building multi-agent systems with type-safe orchestration +--- + +Building a single agent is powerful, but the real potential emerges when agents collaborate. Whether you need specialized expertise, parallel processing, or coordinated workflows, multi-agent systems enable you to break down complex problems into manageable, focused components. + +## Why Multi-Agent Systems? + +Modern AI systems increasingly rely on agent collaboration. The benefits mirror how effective teams work: + +- **Specialization**: Each agent focuses on what it does best +- **Scalability**: Distribute work across multiple agents +- **Modularity**: Add or modify agents without rewriting the system +- **Reliability**: Isolate failures to specific agents + +The difference is in the execution: agents communicate through type-safe calls with automatic validation. + +## Agent Communication in v1 + +Agents communicate via direct, type-safe calls through the context object. The SDK auto-discovers all agents in your project and provides typed access. + +**Basic Pattern:** +```typescript +// Inside any agent handler +const result = await c.agent.enrichmentAgent.run({ + text: input.data +}); +``` + +**Key features:** +- **Auto-discovery**: No manual agent lookup or configuration +- **Type safety**: Full TypeScript inference when schemas are defined +- **Schema validation**: Input and output automatically validated +- **Simple syntax**: Direct property access, no lookup APIs + +### Conditional Routing in Routes + +Routes can analyze requests and conditionally call different agents based on the analysis. This replaces delegation patterns from previous versions with explicit, controllable orchestration: + +```typescript +// route.ts - routes handle conditional logic +router.post('/process', async (c) => { + const body = await c.req.json(); + + // Choose agent based on request type + if (body.type === 'urgent') { + return c.json(await c.agent.urgentProcessor.run(body)); + } + + return c.json(await c.agent.standardProcessor.run(body)); +}); +``` + +This gives you explicit control over which agent handles each request. + +--- + +## Tutorial Steps + +Each step demonstrates a core pattern for agent communication. + +### Step 1: Basic Agent Calls + + + +The foundation of multi-agent systems is simple: one agent calls another. This step demonstrates the basic pattern with a coordinator that uses AI-powered sentiment analysis through a specialist agent. + + + +**What this demonstrates:** +- Type-safe agent calls via `c.agent.enricher.run()` +- Auto-discovery (no agent lookup needed) +- Schema validation on both input and output +- AI-powered enrichment using `generateObject` with structured schemas +- KV storage for tracking interaction history +- Processing results from another agent +- Clean separation between coordinator and specialist logic + +**Try it:** +1. Start DevMode: `agentuity dev` +2. Send a POST request: + ```bash + curl -X POST http://localhost:3500/process \ + -H "Content-Type: application/json" \ + -d '{"text": "I love this product! It works great."}' + ``` +3. Check logs to see both agents executing +4. Notice the enriched response with AI-powered sentiment analysis and confidence scores + +> **Key Insight:** Agent calls are direct property access with full type safety. The SDK discovers all agents automatically and validates all inputs and outputs through schemas. Use specialist agents for AI-powered processing like the enricher's sentiment analysis. + + + +--- + +### Step 2: Sequential Workflows + + + +Many workflows require processing data through multiple stages. This step shows how to chain agents together in a 2-stage pipeline: enrichment followed by summarization. + + + +**What this demonstrates:** +- Chaining multiple agent calls sequentially +- Passing enrichment results to the summarizer +- Building complex workflows from simple, focused agents +- Processing intermediate results before the next step +- Maintaining type safety through the entire chain + +**Try it:** +1. Start DevMode +2. Send a document for processing: + ```bash + curl -X POST http://localhost:3500/pipeline \ + -H "Content-Type: application/json" \ + -d '{"text": "Artificial intelligence is transforming how we build software. Modern AI systems can understand context, generate code, and help developers be more productive than ever before."}' + ``` +3. Watch logs to see each agent execute in order +4. Notice how the enricher's sentiment analysis informs the summarizer's output + +> **Key Insight:** Sequential workflows chain agents together. Each agent focuses on one task, making the system modular and testable. Results flow naturally from one agent to the next, with each stage building on the previous. + + + +--- + +### Step 3: Parallel Execution + + + +When agents don't depend on each other's results, run them in parallel for better performance. This step demonstrates concurrent AI processing with translation and summarization happening simultaneously. + + + +**What this demonstrates:** +- Running multiple agents concurrently with `Promise.all` +- Independent AI operations (translation + summarization) in parallel +- Significant performance improvements over sequential execution +- Type-safe destructuring of parallel results +- Execution time tracking to show performance gains + +**Try it:** +1. Start DevMode +2. Send text for parallel processing: + ```bash + curl -X POST http://localhost:3500/process \ + -H "Content-Type: application/json" \ + -d '{"text": "Artificial intelligence is revolutionizing software development. AI agents can now collaborate, each specializing in different tasks to solve complex problems efficiently.", "targetLanguage": "Spanish"}' + ``` +3. Check execution time in logs - much faster than sequential processing +4. See combined results: original text, translation, and summary + +> **Key Insight:** Parallel execution dramatically improves performance when agents are independent. Use `Promise.all` to run multiple agent calls concurrently. Translation and summarization are perfect examples - they can both analyze the same input independently. + + + +--- + +### Step 4: Smart Routing with Structured Responses + + + +Advanced systems need intelligent routing - using AI to decide which agent should handle each request. Structured responses (validated schemas) transform unpredictable AI output into reliable routing decisions. This step shows a router that directs requests to either support or sales specialists. + + + +**What this demonstrates:** +- AI-powered routing with structured output schemas +- Confidence thresholds for fallback handling +- Type-safe routing decisions validated by Zod +- AI-powered specialist agents (support agent uses AI for response generation) +- KV storage for tracking support tickets +- Severity assessment and suggested actions +- Transforming unpredictable AI output into reliable data structures +- Building production-ready orchestration systems + +**Try it:** +1. Start DevMode +2. Send different request types: + ```bash + # Support request + curl -X POST http://localhost:3500/route \ + -H "Content-Type: application/json" \ + -d '{"message": "I cannot access my account and keep getting error 403"}' + + # Sales inquiry + curl -X POST http://localhost:3500/route \ + -H "Content-Type: application/json" \ + -d '{"message": "What are your enterprise pricing plans?"}' + ``` +3. Check logs to see routing decisions with confidence scores +4. Notice the support agent's AI-generated response with severity and suggested actions +5. Try ambiguous requests to see fallback handling + +> **Key Insight:** Structured responses transform unreliable AI outputs into type-safe data structures. Define schemas with Zod, validate AI decisions, and build reliable routing logic on validated data. Specialist agents can also use AI to provide context-aware responses, as shown in the support agent. + + + +--- + +## Lab: Multi-Agent System + +TODO: add concierge lab example +--- + +## Key Takeaways + +By the end of this module, you should understand: + +1. **Type-Safe Agent Calls:** + - Direct access via `c.agent.agentName.run(input)` + - Auto-discovery of all agents in your project + - Full TypeScript inference with schemas + - Automatic input/output validation + - Using AI SDK (`generateObject`) in specialist agents + +2. **Sequential Workflows:** + - Chain agents together for multi-stage processing + - Pass results between agents (enrichment → summarization) + - Build complex workflows from simple, focused components + - Maintain type safety through the entire chain + +3. **Parallel Execution:** + - Run independent agents concurrently with `Promise.all` + - Execute different AI operations simultaneously (translation + summarization) + - Improve performance significantly over sequential processing + - Track execution time to measure performance gains + +4. **Smart Routing:** + - Use AI with structured schemas for routing decisions + - Validate AI output with Zod for reliability + - Implement confidence thresholds and fallback handling + - Build AI-powered specialist agents with context-aware responses + - Use KV storage to track interactions and state + - Build production-ready orchestration systems + +Multi-agent systems enable specialization, scalability, and modularity. The SDK provides type-safe communication with automatic validation, making it simple to build complex orchestration patterns. Combine with AI SDK and KV storage for powerful, stateful multi-agent applications. + +For organizing related agents into hierarchies, see **Module 8: Subagents & Hierarchies**. + +--- + +## What's Next? + +**Module 7: Observability, Evals, & Validation** - Learn how to monitor agent execution, implement quality checks, and ensure your multi-agent systems behave reliably in production. + +For more details on agent communication patterns and advanced orchestration, see: +- [Agent Communication Guide](/Guides/agent-communication) +- [Subagents Guide](/Guides/subagents) (covered in Module 8) diff --git a/content/v1/Training/07-observability-evals-validation/index.mdx b/content/v1/Training/07-observability-evals-validation/index.mdx new file mode 100644 index 00000000..c6f26198 --- /dev/null +++ b/content/v1/Training/07-observability-evals-validation/index.mdx @@ -0,0 +1,335 @@ +--- +title: "Module 7: Observability, Evals, & Validation" +description: Build production-ready agents with quality checks, monitoring, and validation +--- + +You've built agents that can reason, remember, and collaborate. Now it's time to make them reliable and safe for production use. + +## The Production Reality + +When [Salesforce deployed their Agentforce AI agents](https://www.hr-brew.com/stories/2025/03/04/salesforce-ai-agents-reskilling), they discovered that success required more than technology—it demanded comprehensive guardrails, systematic evaluation, and robust observability. According to [NIST's AI Risk Management framework](https://www.nist.gov/itl/ai-risk-management-framework), the primary operational risks in AI systems include hallucination, prompt injection, resource consumption, and compliance drift. + +The gap between demo and production? Proper validation, quality checks, and comprehensive monitoring. + +## Three Pillars for Production Agents + +### Observability + +Agentuity provides automatic OpenTelemetry integration with zero configuration. All agent executions, LLM calls, and storage operations are traced automatically and appear in the Sessions tab with timeline visualization. + +Custom spans, structured logging, and child loggers give you deep visibility into agent behavior. + +### Validation + +Schema-based validation handles most cases automatically—input and output validation happens before and after your handler executes. Advanced patterns with Zod (refinements, transforms, cross-field validation) handle complex business rules. + +### Quality Assurance + +The evaluations system lets you define automated quality checks that run after agent execution. From simple confidence thresholds to complex LLM-as-judge patterns, evaluations ensure your agents meet production standards. + +--- + +## Tutorial Steps + +Each step demonstrates a core pattern for production-ready agents. + +### Step 1: Basic Evaluations + + + +Evaluations run automated quality checks after your agent executes. This step shows a simple binary pass/fail evaluation for a sentiment analyzer. + + + +**What this demonstrates:** +- Creating evaluations with `agent.createEval()` +- Binary pass/fail pattern +- Access to input and output in eval handler +- Metadata tracking for debugging +- Non-blocking execution (runs after response sent) + +**Try it:** +1. Start DevMode: `agentuity dev` +2. Send text: `{"text": "This product is amazing!"}` +3. Check logs for eval results showing high confidence +4. Send ambiguous text: `{"text": "It's okay I guess"}` +5. Notice response returns immediately—eval runs after + +> **Key Insight:** Evaluations run asynchronously after the response is sent using `waitUntil()`. They don't block your agent's response but provide crucial quality signals for monitoring and improvement. + + + +--- + +### Step 2: Advanced Evaluations + + + +Advanced evaluations use LLM-as-judge patterns for complex quality checks. This step shows a content moderator with score-based safety evaluation. + + + +**What this demonstrates:** +- Score-based evaluations (0-1 range) +- LLM-as-judge pattern with structured output +- Using `generateObject` within eval handlers +- Detailed metadata with reasoning +- Safety and compliance checking + +**Try it:** +1. Send safe content: `{"content": "How do I reset my password?"}` +2. Check logs for high safety scores +3. Send potentially problematic content +4. Notice detailed reasoning in eval metadata + +> **Key Insight:** LLM-as-judge evaluations use AI to assess quality, safety, or task completion. Return score-based results (0-1) for nuanced quality tracking, not just binary pass/fail. + + + +--- + +### Step 3: Event System + + + +The event system provides lifecycle hooks for monitoring agent execution. This step shows agent-level events for performance tracking. + + + +**What this demonstrates:** +- Agent-level event listeners (started, completed, errored) +- Performance monitoring with timing +- State access in event handlers +- Error tracking and logging +- Threshold-based alerting + +**Try it:** +1. Send requests and watch execution logs +2. Notice timing information in completed events +3. Check for slow execution warnings +4. Try triggering errors to see error events + +> **Key Insight:** Events execute sequentially during the agent lifecycle. Use them for monitoring, analytics, and alerting. For non-blocking work in event handlers, use `c.waitUntil()`. + + + +--- + +### Step 4: Structured Logging + + + +Child loggers attach context automatically, making logs easier to filter and analyze. This step shows component-based logging in a data processor. + + + +**What this demonstrates:** +- Creating child loggers with `c.logger.child()` +- Component-based context propagation +- Different log levels (info, debug, warn) +- Structured logging with objects +- Filtering logs by component + +**Try it:** +1. Send valid data: `{"data": ["a", "b", "c"], "userId": "123"}` +2. Send data with empty strings to trigger warnings +3. Filter DevMode logs by `component:validation` +4. Notice how component context appears in every log + +> **Key Insight:** Child loggers propagate context automatically. Create them once at the start of different workflow phases, and all subsequent logs include that context—making debugging and filtering much easier. + + + +--- + +### Step 5: Advanced Validation + + + +Schema validation goes beyond simple type checking. This step shows transforms, refinements, and cross-field validation for user input. + + + +**What this demonstrates:** +- String transformations (toLowerCase, trim) +- Type transformations (string to number) +- Custom refinements for business rules +- Cross-field validation between properties +- Automatic validation before handler execution + +**Try it:** +1. Send input with uppercase email: `{"email": "USER@EXAMPLE.COM", ...}` +2. Notice email automatically lowercased +3. Try invalid password (no uppercase or number) +4. Try invalid dates (endDate before startDate) +5. See validation errors before handler runs + +> **Key Insight:** Transformations run during validation, modifying data before your handler receives it. Refinements add custom business rules beyond basic type checking. Both execute automatically—your handler only sees valid, transformed data. + + + +--- + +### Step 6: Custom Spans & Tracing + + + +OpenTelemetry integration provides automatic tracing. Custom spans let you track specific operations within your agent's business logic. + + + +**What this demonstrates:** +- Creating custom spans with `c.tracer.startActiveSpan()` +- Nested spans (parent-child hierarchy) +- Setting span attributes for metadata +- Adding events to mark milestones +- Recording exceptions and setting status +- Always calling `span.end()` in finally blocks + +**Try it:** +1. Send data batch: `{"data": ["item1", "item2", ""]}` +2. Open DevMode Sessions tab +3. View span timeline showing nested execution +4. Check span attributes and events +5. See parent-child span relationships + +> **Key Insight:** Spans create hierarchical traces of your agent's execution. Use attributes for metadata, events for milestones, and always end spans in finally blocks. The Sessions tab visualizes the complete execution timeline. + + + +--- + +## Advanced Patterns + +The tutorial steps cover core observability, evaluation, and validation concepts. Here are additional patterns for complex production scenarios. + +### Content Moderation with LLM Jury + +For applications requiring compliance validation (finance, healthcare, customer support), combine structural validation with AI-powered content evaluation: + +```typescript +// First: validate structure +const result = InputSchema.safeParse(data); +if (!result.success) return { error: result.error }; + +// Second: evaluate content appropriateness +const evaluation = await generateObject({ + model: anthropic('claude-sonnet-4-5'), + schema: ContentEvaluationSchema, + prompt: `Evaluate for compliance: "${result.data.query}"` +}); + +if (!evaluation.object.approved) { + return { error: 'Content policy violation' }; +} + +// Third: process validated and approved data +return await processQuery(result.data); +``` + +This two-stage validation ensures both structural validity and content appropriateness before processing. + +### External API Validation + +When working with external APIs, defensive validation prevents downstream errors from malformed responses: + +```typescript +const rawData = await fetchExternalAPI(); + +// Validate with Zod +const parseResult = ExternalDataSchema.safeParse(rawData); + +if (parseResult.success) { + await processData(parseResult.data); +} else { + c.logger.warn('Invalid external data', { + errors: parseResult.error.issues + }); + // Fallback or error handling +} +``` + +Always validate external data—don't trust that APIs return what their documentation claims. + +### Metrics and Golden Datasets + +Build evaluation datasets covering critical scenarios: +- Input examples representing edge cases +- Expected behavior for each scenario +- Success criteria for validation + +Track metrics that matter for your domain: +- Accuracy (compare against test cases) +- Compliance (check output validation pass rates) +- Performance (track timing in telemetry) +- Cost (monitor token usage per request) + +Use evaluations to measure these metrics automatically and catch regressions as you refine your agents. + +--- + +## Key Takeaways + +By the end of this module, you should understand: + +1. **Evaluations:** + - Create automated quality checks with `agent.createEval()` + - Binary pass/fail or score-based (0-1) patterns + - LLM-as-judge for complex quality assessment + - Non-blocking execution via `waitUntil()` + +2. **Events:** + - Lifecycle hooks at agent level (started, completed, errored) + - Performance monitoring and error tracking + - State access in event handlers + - Sequential execution for reliability + +3. **Structured Logging:** + - Child loggers with automatic context propagation + - Component-based organization + - Different log levels for appropriate visibility + - Filtering and searching in production + +4. **Advanced Validation:** + - Automatic schema validation (input/output) + - Transformations modify data during validation + - Refinements add custom business rules + - Cross-field validation for complex constraints + +5. **Custom Spans:** + - OpenTelemetry integration (automatic, zero config) + - Custom spans for business logic tracing + - Nested spans for hierarchical operations + - Attributes, events, and exception recording + +All observability features are non-blocking and type-safe, designed for production use from day one. + +--- + +## What's Next? + +**Module 8: Subagents & Hierarchies** - Learn how to organize related agents into parent-child hierarchies for better code organization and reusability. + +For deeper dives into these concepts, see: +- [Evaluations Guide](/Guides/evaluations) +- [Events Guide](/Guides/events) +- [Logging Guide](/Guides/logging) +- [Schema Validation Guide](/Guides/schema-validation) diff --git a/content/v1/Training/08-deployment/index.mdx b/content/v1/Training/08-deployment/index.mdx new file mode 100644 index 00000000..695a7340 --- /dev/null +++ b/content/v1/Training/08-deployment/index.mdx @@ -0,0 +1,429 @@ +--- +title: "Module 8: Deployment" +description: From local development to production - deploy agents anywhere +--- + +You've built agents with state management, multi-agent collaboration, and production-grade quality checks. Now it's time to deploy them. + +## Deployment Philosophy + +Write your agent code once, deploy it anywhere. The same code runs in local development, public cloud, private cloud, multi-cloud, on-premises, or edge deployments. No code changes required between environments. + +### How It Works + +The SDK is deployment-agnostic by design. Your agent code stays identical across all deployment targets because the SDK automatically adapts based on environment: + +**Local Development (`agentuity dev`):** +- All services (KV, vector, object storage) run locally on your machine +- No authentication required +- Same APIs as production + +**Production Deployments:** +- Services connect to configured endpoints +- Same API calls, different backend infrastructure +- Automatic authentication and encryption + +**Deployment Targets:** + +Each deployment type uses the same agent code but points services to different infrastructure: + +- **Public Cloud**: Services at Agentuity-managed infrastructure +- **Private Cloud/Multi-Cloud**: Services in your VPC +- **On-Premises**: Services in your datacenter +- **Edge**: Services on local or edge devices + +The SDK handles the differences automatically. For non-public-cloud deployments, you configure where services run via environment variables. + +## Environment Progression + +Every agent follows the same development path: + +``` +Local Development → Preview → Staging → Production +``` + +### Local Development + +DevMode (`agentuity dev`) provides the complete Agentuity experience on your machine: + +- **Hot Reload**: Instant feedback on code changes +- **Full Observability**: Sessions tab shows execution traces +- **Real Features**: Test with actual storage, state, and agent communication +- **Fast Iteration**: Edit, save, test immediately + +This is where you build and debug agents. Everything works exactly as it will in production. + +### Preview Deployments + +Preview deployments test your changes in the cloud automatically when you create a pull request: + +- **Automatic**: Triggered on PR creation +- **Unique URLs**: Each preview gets a URL with git hash identifier +- **Cloud Testing**: Validate changes in production-like infrastructure +- **Team Collaboration**: Share preview links with stakeholders + +Preview deployments catch issues before they reach production and enable confident merging. + +### Staging Environment + +Staging provides production-like infrastructure for final validation: + +- **Load Testing**: Test with realistic data volumes +- **Integration Testing**: Validate with external services +- **Performance Validation**: Ensure agents meet latency requirements +- **Configuration Testing**: Verify environment-specific settings + +Use staging to validate changes under production conditions before deploying to users. + +### Production + +Production is where your agents serve real users: + +- **Auto-Scaling**: Infrastructure scales with demand +- **Complete Observability**: Same debugging experience as development +- **Regional Options**: Deploy to multiple regions +- **Monitoring**: Track performance and errors + +Production deployments can target any of Agentuity's deployment options based on your requirements. + +### Environment Validation + +Different environments focus on different validation aspects: + +| Focus | Development | Preview | Staging | Production | +|-------|------------|---------|---------|------------| +| **Core Features** | ✓ Works | ✓ Change validation | ✓ Load testing | ✓ Success metrics | +| **Performance** | Basic testing | Cloud behavior | Stress testing | Response time | +| **Integration** | Mock APIs | Real APIs | Full integration | User experience | +| **Observability** | Debug info | Cloud traces | Telemetry accuracy | Performance insights | +| **Resilience** | Basic recovery | Team testing | Failover tests | Incident response | + +--- + +## Deployment Options + +Agentuity supports five deployment targets. Choose based on your requirements—your agent code works everywhere without changes. + +### Public Cloud + +**Managed infrastructure with automatic scaling** + +Agentuity-managed infrastructure with global edge deployment: + +- Deploy with one command +- Automatic scaling from zero to thousands of requests +- Sub-100ms cold starts +- Multi-region load balancing + +**Use when:** +- Getting started with Agentuity +- Building MVPs or prototypes +- Standard compliance requirements are sufficient +- You want to focus on agent development rather than infrastructure + +--- + +### Private Cloud + +**Deploy to your own VPC** + +Run agents in your own cloud infrastructure: + +- Full infrastructure control +- Data never leaves your VPC +- Automatic scaling within your environment +- Supports AWS, Azure, and GCP + +**Use when:** +- You have data sovereignty requirements +- Regulatory compliance requires it (HIPAA, SOC2, etc.) +- You need custom networking or security policies +- Your organization requires infrastructure control + +--- + +### Multi-Cloud + +**Deploy across multiple cloud providers** + +Run agents across different cloud providers: + +- Deploy to any combination of AWS, Azure, GCP +- Data stays within your chosen infrastructure +- Mix providers based on region, features, or cost +- Automatic scaling per provider + +**Use when:** +- You want to avoid vendor lock-in +- You need provider-specific features in different regions +- You require multi-region redundancy across providers +- Your organization has a multi-cloud strategy + +--- + +### On-Premises + +**Deploy to your own datacenter** + +Run agents entirely within your own infrastructure: + +- Complete control over hardware and networking +- Data never leaves your infrastructure +- Supports VMware, Kubernetes, and bare metal +- Air-gapped deployments supported + +**Use when:** +- You have air-gapped environment requirements +- You need complete data control +- You're integrating with legacy systems +- Government or defense requirements mandate it + +--- + +### Edge + +**Deploy to edge devices** + +Run agents at the network edge or on local devices: + +- Deploy to Raspberry Pi, laptops, or edge servers +- Data processed locally +- Optional sync with central services +- Minimal latency for local operations + +**Use when:** +- You need sub-10ms latency +- You're building IoT or embedded applications +- You need offline-first capabilities +- You're processing data at the source (retail, manufacturing, field deployments) + +--- + +## Regional Deployment + +For applications requiring global reach, deploy to multiple regions simultaneously. Regional deployment enables load balancing between regions and provides failover capabilities for high availability. + +Use regional deployment for global SaaS applications, high-availability requirements, and latency-sensitive applications with worldwide users. + +--- + +## Choosing Your Deployment + +**Decision Framework:** + +| Requirement | Recommended Option | +|-------------|-------------------| +| Just getting started | Public Cloud | +| HIPAA/SOC2 compliance | Private Cloud | +| Air-gapped networks | On-Premises | +| Multi-region redundancy | Multi-Cloud | +| Sub-10ms latency | Edge | +| Data sovereignty | Private Cloud or On-Premises | +| Vendor lock-in concerns | Multi-Cloud | +| Global scale + speed | Public Cloud (multi-region) | + +**Recommendation:** + +Start with **Public Cloud** for development and initial production. It requires minimal configuration and provides the complete Agentuity feature set. + +**Evolution path:** + +As requirements evolve, you can migrate or expand to other deployment options: +- Move to **Private Cloud** when compliance requires it +- Add **Multi-Cloud** for redundancy or provider flexibility +- Use **On-Premises** when data sovereignty is mandatory +- Deploy to **Edge** when latency is critical + +**Typical progression:** + +1. **Development**: Local development with `agentuity dev` +2. **Initial Production**: Public cloud deployment +3. **Scale & Compliance**: Private cloud or multi-cloud as requirements dictate +4. **Specialized Needs**: Edge or on-premises for specific use cases + +--- + +## Real-World Deployment Patterns + +### Enterprise Scale: Salesforce + +When Salesforce deployed agents to handle 50% of support cases, they learned critical lessons about production deployment: + +**Gradual Rollout:** +- Started with 1% of tickets +- Monitored closely and iterated +- Expanded to 50% over time + +**Human Oversight:** +- Agents flagged uncertain responses initially +- Support staff validated agent decisions +- Continuous improvement based on feedback + +**Key Lessons:** +- Start small and expand gradually +- Monitor closely during initial rollout +- Plan for organizational change, not just technical deployment +- Iterate based on real-world performance + +### Financial Services: Compliance First + +[Over 100 GenAI applications are being deployed in financial services](https://www.cbinsights.com/research/report/generative-ai-financial-services-applications-2025/). Banks deploying AI agents face strict requirements: + +**Audit Trails:** +- Every agent interaction needs logging +- Complete history for regulatory review +- Compliance validation at every step + +**Failover Critical:** +- Instant human handoff when confidence low +- No single point of failure +- Continuous availability required + +**Data Security:** +- No customer PII in prompts +- Encryption at rest and in transit +- Access controls and authentication + +**Deployment Choice:** Most financial institutions use **Private Cloud** for compliance and data sovereignty. + +### Healthcare: HIPAA Requirements + +Healthcare organizations deploying diagnostic assistance agents must meet strict data protection requirements: + +**Data Residency:** +- Patient data cannot leave specific regions +- Complete audit logs required +- Encryption mandatory + +**Access Controls:** +- Strict authentication and authorization +- Role-based access to patient information +- Audit every data access + +**Deployment Choice:** **Private Cloud** or **On-Premises** for complete control over patient data. + +### Global SaaS: Performance at Scale + +Global applications require worldwide performance: + +**Regional Deployment:** +- Agents deployed in multiple regions +- Users routed to nearest region +- Reduced latency worldwide + +**High Availability:** +- Automatic failover between regions +- No single point of failure +- Continuous monitoring + +**Deployment Choice:** **Public Cloud** with regional deployment or **Multi-Cloud** for provider flexibility. + +--- + +## Best Practices + +### Deployment Strategy + +**Preview First:** +- Always test in preview deployments before production +- Share preview URLs with stakeholders +- Validate changes in cloud environment +- Catch issues before they affect users + +**Gradual Rollouts:** +- Start with small percentage of traffic (1-5%) +- Monitor error rates and performance +- Gradually increase (10% → 25% → 50% → 100%) +- Roll back immediately if issues detected + +**Environment Isolation:** +- Use separate credentials per environment +- Never use production keys in development +- Test thoroughly in each environment +- Validate performance under realistic load + +### Monitoring & Observability + +**Same Tools Everywhere:** +- Identical observability across all environments +- Sessions tab works in production +- Same debugging experience everywhere + +**Track Key Metrics:** +- Latency and response times +- Error rates and types +- Throughput and request volumes +- Resource utilization + +**Set Up Alerts:** +- Monitor for anomalies +- Alert on error rate spikes +- Track performance degradation +- Automatic notifications for critical issues + +### Security + +**Credentials Management:** +- Separate secrets per environment +- Rotate credentials regularly +- Use least-privilege access +- Never commit secrets to code + +**Data Protection:** +- Encryption at rest and in transit +- Access controls on sensitive data +- Audit logs for compliance +- Regular security reviews + +### Testing + +**Validate Each Environment:** +- Test in development first +- Validate in preview deployments +- Load test in staging +- Monitor closely in production + +**Comprehensive Testing:** +- Functional correctness +- Performance under load +- Failover and recovery +- Integration with external services + +--- + +## Key Takeaways + +By the end of this module, you should understand: + +1. **Environment Progression:** + - Development → Preview → Staging → Production + - Each environment serves specific validation purposes + - Same code works across all environments + +2. **Deployment Options:** + - Public Cloud: Speed and simplicity + - Private Cloud: Control and security + - Multi-Cloud: Flexibility and redundancy + - On-Premises: Complete sovereignty + - Edge: Ultra-low latency + +3. **Decision Framework:** + - Start with public cloud for most use cases + - Add other options as requirements evolve + - Choose based on compliance, performance, and control needs + +4. **Best Practices:** + - Preview deployments before production + - Gradual rollouts with monitoring + - Environment isolation and security + - Comprehensive testing at each stage + +TODO: Add deployment commands and configuration details for each deployment option. + +--- + +## What's Next? + +You've learned how to deploy agents from local development to production across multiple deployment targets. + +**Module 9: Capstone Project** - Put everything together by building a complete multi-agent system that demonstrates all the concepts from this training series. diff --git a/content/v1/Training/09-sandbox-capstone/index.mdx b/content/v1/Training/09-sandbox-capstone/index.mdx new file mode 100644 index 00000000..45a66713 --- /dev/null +++ b/content/v1/Training/09-sandbox-capstone/index.mdx @@ -0,0 +1,283 @@ +--- +title: "Module 9: Advanced Multi-Agent Research System" +description: Building sophisticated agent coordination with recursive research +--- + +Time to build an advanced multi-agent research system that combines everything you've learned. + +## Building Your Capstone + +As you work on your capstone project: + +1. **Review previous modules** - Each contains pieces you'll need +2. **Start simple** - Get basic flow working first +3. **Iterate** - Add features incrementally +4. **Test thoroughly** - Each agent independently, then together +5. **Monitor everything** - You can't fix what you can't see + + +Pro Tip: Build your research system iteratively. Start with orchestrator + web search agents, get that working perfectly, then add recursive research and report generation. + + +## Coming Soon: Agentic Sandbox + + +The Agentic Sandbox - an interactive environment where you can experiment with agents risk-free, test complex scenarios, and learn through hands-on exploration. + + +### What is the Agentic Sandbox? + +A safe, isolated environment for agent experimentation: +- **Pre-configured scenarios**: Common agent patterns ready to explore +- **Interactive debugging**: Step through agent decisions +- **Instant reset**: Break things and start fresh +- **Guided challenges**: Progressive exercises with hints + +### Sandbox Features (Coming Soon) + +- Mock data sources and APIs +- Simulated user interactions +- Time travel debugging +- Performance profiling +- Collaborative sessions + +## Coming Soon: Training Agent + + +Your AI-powered learning companion that adapts to your progress, answers questions, and provides personalized guidance through the Agentuity platform. + + +### Your Personal Agent Instructor + +An intelligent assistant that helps you learn: +- **Contextual help**: Understands what you're building +- **Debugging assistance**: Helps identify and fix issues +- **Learning paths**: Customized based on your experience +- **Best practices**: Recommends patterns for your use case + +## Capstone Project: Deep Research System + +Build an advanced multi-agent research system that demonstrates mastery of sophisticated agent coordination, recursive algorithms, and real-world API integration. + +### Project Overview + +**Goal**: Create a research system that can: +- Coordinate multiple specialized research agents +- Conduct recursive deep-dive investigations +- Accumulate knowledge across research iterations +- Generate comprehensive structured reports +- Handle complex multi-source information synthesis + +### System Architecture + +The research system coordinates four specialized agents working together: + +| Agent | Role | +|-------|------| +| **Orchestrator** | Workflow coordination and error handling | +| **Researcher** | Recursive research with learning accumulation | +| **Web Search** | Intelligent search with AI-powered relevance filtering | +| **Author** | Report synthesis and markdown generation | + + + +### Skills Progression + +This sophisticated system builds on concepts from every previous module: + +| Module | Skills Applied in Deep Research | +|--------|--------------------------------| +| **Module 1** | Agent fundamentals, request handling → Complex orchestration | +| **Module 2** | Handler patterns, routing → Agent coordination | +| **Module 3** | Specialized routes, triggers → Advanced orchestration patterns | +| **Module 4** | State management → Research state accumulation | +| **Module 5** | Storage APIs, vector search → Knowledge accumulation | +| **Module 6** | Multi-agent communication → Advanced coordination | +| **Module 7** | Validation, evaluations → Research quality assurance | +| **Module 8** | Deployment → Production research system | + +### Project Structure + +The research system uses the two-file pattern for each agent: + +``` +src/agents/ +├── research-orchestrator/ +│ ├── agent.ts # Workflow coordination logic +│ └── route.ts # POST /research endpoint +├── deep-researcher/ +│ ├── agent.ts # Recursive research algorithm +│ └── route.ts # Agent-to-agent communication +├── web-search/ +│ ├── agent.ts # Exa API integration + filtering +│ └── route.ts # Search endpoint +└── report-author/ + ├── agent.ts # Report generation with LLM + └── route.ts # Author endpoint +``` + + +The code examples below demonstrate key patterns from the Deep Research system. They're simplified to highlight core concepts. For complete implementations with all helper functions, schemas, and error handling, see the [full GitHub example](#build-this-project-yourself). + + +### Key Implementation: Research Orchestration + +The orchestrator is the system's entry point. It receives research requests with configurable depth and breadth parameters, then coordinates the researcher and author agents sequentially using the coordination patterns from Module 6. + +TODO: Add v1 code example showing: +- createAgent with input/output schemas +- c.agent.deepResearcher.run() pattern +- Error handling with try/catch +- Direct return instead of response.json() + + +```typescript +// TODO: Create step1-orchestration.ts example +// Key concepts to demonstrate: +// 1. Schema definition with Zod (input: query/depth/breadth, output: report) +// 2. Sequential agent coordination: researcher → author +// 3. Type-safe agent calls via c.agent.x.run() +// 4. Error handling for multi-agent workflows +``` + +Notice that the orchestrator manages the workflow without knowing implementation details of the researcher or author agents. The schema validation ensures type safety across agent boundaries. + +### Key Implementation: Recursive Research Pattern + +The researcher implements *recursive research* with controlled depth and breadth. It generates multiple search queries at each level, extracts learnings from results, and recursively explores follow-up questions - with breadth reducing by half each level to prevent exponential growth. + +TODO: Add v1 code example showing: +- createAgent with Research output schema +- Recursive function that accepts AgentContext (c) +- c.agent.webSearch.run() within recursion +- Depth and breadth control (base cases) +- Learning accumulation + +```typescript +// TODO: Create step2-recursive-research.ts example +// Key concepts to demonstrate: +// 1. Pass AgentContext (c) through recursive calls +// 2. Base cases: depth === 0 or max results reached +// 3. Call c.agent.webSearch.run() for each query +// 4. Accumulate results and learnings +// 5. Reduce breadth by half each level (Math.ceil(breadth / 2)) +``` + +Notice how the base cases prevent runaway recursion (depth limit and max results), while the reflection prompt refines queries based on accumulated learnings for deeper investigation. + +### Key Implementation: Web Search with Evaluation + +The web search agent integrates the [Exa API](https://docs.exa.ai/reference/getting-started) for web search, then uses AI to evaluate each result's relevance to the query. This filtering ensures only high-quality, non-duplicate sources accumulate across recursive research calls. + +In v1, we can also add an **evaluation** to validate search quality: + +TODO: Add v1 code example showing: +- createAgent with search input/output schemas +- Exa API integration +- AI-powered relevance filtering +- c.state for storing search metadata +- createEval for quality validation + +```typescript +// TODO: Create step3-search-with-eval.ts example +// Key concepts to demonstrate: +// 1. External API integration (Exa) +// 2. Duplicate detection using accumulated sources +// 3. AI evaluation for relevance filtering +// 4. Store metadata in c.state for eval +// 5. createEval to validate search quality (min results, diversity) +``` + +The combination of quick filters (duplicate URLs, content length) and AI evaluation balances quality with performance. The evaluation framework validates that searches return relevant, diverse results. + +### Key Implementation: Report Generation + +The author agent takes accumulated research data and generates a comprehensive markdown report. In v1, this can leverage streaming for better user experience: + +TODO: Add v1 code example showing: +- createAgent with stream: true +- LLM integration (Vercel AI SDK) +- Streaming report generation +- Structured prompt building from research data + +```typescript +// TODO: Create step4-report-generation.ts example (optional) +// Key concepts to demonstrate: +// 1. Schema with stream: true for streaming responses +// 2. Vercel AI SDK streamText integration +// 3. Building comprehensive prompts from research data +// 4. Returning text stream directly +``` + +### Build This Project Yourself + +Ready to implement this project? Follow our complete example: + +TODO: Update GitHub link to v1 example repository + +### What This System Demonstrates + +- **Multi-agent orchestration**: Coordinated workflow between orchestrator, researcher, author, and web search agents +- **Recursive algorithms**: Deep research with learning accumulation and follow-up question generation +- **External API integration**: Exa web search API with AI-powered relevance evaluation +- **State accumulation**: Research results, learnings, and queries accumulated across recursive calls +- **Structured outputs**: Zod schemas for search queries, research data, and type-safe agent communication +- **Evaluation framework**: Quality validation for search results and research completeness +- **Production patterns**: Error handling, agent validation, comprehensive logging, and streaming responses + +The complete example shows how to build sophisticated multi-agent research systems that demonstrate mastery of advanced agent concepts from Modules 1-8. + +## Testing Your Deep Research System + +1. **Start DevMode:** +```bash +agentuity dev +``` + +2. **Test research requests:** + - **Simple topics**: "Benefits of renewable energy" + - **Complex topics**: "Impact of quantum computing on cryptography" + - **Technical subjects**: "Latest developments in machine learning architectures" + +3. **Monitor the orchestration:** + - Watch logs to see agent coordination and recursive research calls + - Observe how research accumulates across multiple iterations + - See report generation synthesis from accumulated findings + - Check evaluation results for search quality metrics + +## Key Takeaways + +By building this deep research system, you've mastered: + +- **Multi-agent orchestration**: Coordinating specialized agents for complex workflows +- **Recursive algorithms**: Self-improving systems with learning accumulation +- **Production integration**: Real-world APIs with intelligent filtering and caching +- **Advanced state management**: Accumulation across multiple agent interactions +- **Type safety**: Schema-driven development with full type inference +- **Quality assurance**: Evaluation framework for validating agent outputs +- **Sophisticated observability**: Comprehensive tracking of complex agent workflows + +You're now ready to build advanced production agent systems with Agentuity! + +## What's Next? + +- Deploy your research system to production +- Experiment with different research domains +- Build agents for your own complex use cases +- Share your implementations with the community + +Welcome to advanced agent development! + +--- diff --git a/examples/training/06-agent-communication/step1-basic-agent-calls/coordinator/agent.ts b/examples/training/06-agent-communication/step1-basic-agent-calls/coordinator/agent.ts new file mode 100644 index 00000000..3cd56064 --- /dev/null +++ b/examples/training/06-agent-communication/step1-basic-agent-calls/coordinator/agent.ts @@ -0,0 +1,48 @@ +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +export const agent = createAgent({ + schema: { + input: z.object({ + text: z.string() + }), + output: z.object({ + original: z.string(), + enriched: z.object({ + text: z.string(), + sentiment: z.enum(['positive', 'negative', 'neutral']), + confidence: z.number(), + processedAt: z.string() + }) + }) + }, + metadata: { + name: 'Coordinator', + description: 'Coordinates with enricher agent and tracks history' + }, + handler: async (c, input) => { + c.logger.info('Coordinator processing request', { text: input.text }); + + // Store request (in KV storage) for analytics + await c.kv.set('enrichment-history', c.sessionId, { + text: input.text, + timestamp: new Date().toISOString() + }); + + // Call enricher agent with type-safe access + const enriched = await c.agent.enricher.run({ + text: input.text + }); + + c.logger.info('Received enriched data', { + sentiment: enriched.sentiment, + confidence: enriched.confidence + }); + + // Return combined results + return { + original: input.text, + enriched + }; + } +}); diff --git a/examples/training/06-agent-communication/step1-basic-agent-calls/coordinator/route.ts b/examples/training/06-agent-communication/step1-basic-agent-calls/coordinator/route.ts new file mode 100644 index 00000000..ad144ffe --- /dev/null +++ b/examples/training/06-agent-communication/step1-basic-agent-calls/coordinator/route.ts @@ -0,0 +1,13 @@ +import { createRouter } from '@agentuity/runtime'; + +export const router = createRouter(); + +// Simple POST endpoint that calls the coordinator agent +router.post('/process', async (c) => { + const body = await c.req.json(); + + // Call coordinator agent (which calls enricher internally) + const result = await c.agent.coordinator.run(body); + + return c.json(result); +}); diff --git a/examples/training/06-agent-communication/step1-basic-agent-calls/enricher/agent.ts b/examples/training/06-agent-communication/step1-basic-agent-calls/enricher/agent.ts new file mode 100644 index 00000000..dc891fbd --- /dev/null +++ b/examples/training/06-agent-communication/step1-basic-agent-calls/enricher/agent.ts @@ -0,0 +1,47 @@ +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; +import { generateObject } from 'ai'; +import { anthropic } from '@ai-sdk/anthropic'; + +export const agent = createAgent({ + schema: { + input: z.object({ + text: z.string() + }), + output: z.object({ + text: z.string(), + sentiment: z.enum(['positive', 'negative', 'neutral']), + confidence: z.number(), + processedAt: z.string() + }) + }, + metadata: { + name: 'Enricher', + description: 'Enriches text with AI-powered sentiment analysis' + }, + handler: async (c, input) => { + c.logger.info('Enricher analyzing text sentiment', { length: input.text.length }); + + // Use AI to analyze sentiment + const { object: analysis } = await generateObject({ + model: anthropic('claude-sonnet-4-5'), + schema: z.object({ + sentiment: z.enum(['positive', 'negative', 'neutral']), + confidence: z.number().min(0).max(1) + }), + prompt: `Analyze the sentiment of this text and provide a confidence score: "${input.text}"` + }); + + c.logger.info('Sentiment analysis complete', { + sentiment: analysis.sentiment, + confidence: analysis.confidence + }); + + return { + text: input.text, + sentiment: analysis.sentiment, + confidence: analysis.confidence, + processedAt: new Date().toISOString() + }; + } +}); diff --git a/examples/training/06-agent-communication/step1-basic-agent-calls/enricher/route.ts b/examples/training/06-agent-communication/step1-basic-agent-calls/enricher/route.ts new file mode 100644 index 00000000..0247d6c2 --- /dev/null +++ b/examples/training/06-agent-communication/step1-basic-agent-calls/enricher/route.ts @@ -0,0 +1,10 @@ +import { createRouter } from '@agentuity/runtime'; + +export const router = createRouter(); + +// Direct access to enricher (optional - mainly called by coordinator) +router.post('/enrich', async (c) => { + const body = await c.req.json(); + const result = await c.agent.enricher.run(body); + return c.json(result); +}); diff --git a/examples/training/06-agent-communication/step2-sequential-workflows/coordinator/agent.ts b/examples/training/06-agent-communication/step2-sequential-workflows/coordinator/agent.ts new file mode 100644 index 00000000..ade97344 --- /dev/null +++ b/examples/training/06-agent-communication/step2-sequential-workflows/coordinator/agent.ts @@ -0,0 +1,53 @@ +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +export const agent = createAgent({ + schema: { + input: z.object({ + text: z.string() + }), + output: z.object({ + original: z.string(), + enriched: z.object({ + sentiment: z.enum(['positive', 'negative', 'neutral']), + confidence: z.number(), + keyPhrases: z.array(z.string()) + }), + summary: z.object({ + text: z.string(), + wordCount: z.number() + }) + }) + }, + metadata: { + name: 'Pipeline Coordinator', + description: 'Orchestrates sequential document processing through enrichment and summarization' + }, + handler: async (c, input) => { + c.logger.info('Starting sequential pipeline', { text: input.text }); + + // Stage 1: Enrich the text with AI-powered analysis + const enriched = await c.agent.enricher.run({ + text: input.text + }); + c.logger.info('Enrichment complete', { + sentiment: enriched.sentiment, + confidence: enriched.confidence + }); + + // Stage 2: Generate summary using enrichment context + const summary = await c.agent.summarizer.run({ + text: input.text, + sentiment: enriched.sentiment, + keyPhrases: enriched.keyPhrases + }); + c.logger.info('Summarization complete', { wordCount: summary.wordCount }); + + // Return results from both stages + return { + original: input.text, + enriched, + summary + }; + } +}); diff --git a/examples/training/06-agent-communication/step2-sequential-workflows/coordinator/route.ts b/examples/training/06-agent-communication/step2-sequential-workflows/coordinator/route.ts new file mode 100644 index 00000000..1a4892ec --- /dev/null +++ b/examples/training/06-agent-communication/step2-sequential-workflows/coordinator/route.ts @@ -0,0 +1,13 @@ +import { createRouter } from '@agentuity/runtime'; + +export const router = createRouter(); + +// Pipeline endpoint that processes documents through multiple stages +router.post('/pipeline', async (c) => { + const body = await c.req.json(); + + // Call coordinator which orchestrates the sequential workflow + const result = await c.agent.pipelineCoordinator.run(body); + + return c.json(result); +}); diff --git a/examples/training/06-agent-communication/step3-parallel-execution/aggregator/agent.ts b/examples/training/06-agent-communication/step3-parallel-execution/aggregator/agent.ts new file mode 100644 index 00000000..9def7a72 --- /dev/null +++ b/examples/training/06-agent-communication/step3-parallel-execution/aggregator/agent.ts @@ -0,0 +1,56 @@ +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +export const agent = createAgent({ + schema: { + input: z.object({ + text: z.string(), + targetLanguage: z.string().default('Spanish') + }), + output: z.object({ + original: z.string(), + translation: z.object({ + language: z.string(), + text: z.string() + }), + summary: z.object({ + text: z.string(), + keyPoints: z.array(z.string()) + }), + executionTime: z.string() + }) + }, + metadata: { + name: 'Content Processor', + description: 'Processes text with parallel translation and summarization' + }, + handler: async (c, input) => { + const startTime = Date.now(); + c.logger.info('Starting parallel processing', { + textLength: input.text.length, + targetLanguage: input.targetLanguage + }); + + // Execute translation and summarization in parallel - much faster than sequential + const [translation, summary] = await Promise.all([ + c.agent.translator.run({ + text: input.text, + targetLanguage: input.targetLanguage + }), + c.agent.summarizer.run({ + text: input.text + }) + ]); + + const executionTime = `${Date.now() - startTime}ms`; + c.logger.info('Parallel processing complete', { executionTime }); + + // Return combined results from both agents + return { + original: input.text, + translation, + summary, + executionTime + }; + } +}); diff --git a/examples/training/06-agent-communication/step3-parallel-execution/aggregator/route.ts b/examples/training/06-agent-communication/step3-parallel-execution/aggregator/route.ts new file mode 100644 index 00000000..2287c8f6 --- /dev/null +++ b/examples/training/06-agent-communication/step3-parallel-execution/aggregator/route.ts @@ -0,0 +1,13 @@ +import { createRouter } from '@agentuity/runtime'; + +export const router = createRouter(); + +// Process endpoint that runs translation and summarization in parallel +router.post('/process', async (c) => { + const body = await c.req.json(); + + // Call processor which runs translation and summarization in parallel + const result = await c.agent.contentProcessor.run(body); + + return c.json(result); +}); diff --git a/examples/training/06-agent-communication/step4-smart-routing/router/agent.ts b/examples/training/06-agent-communication/step4-smart-routing/router/agent.ts new file mode 100644 index 00000000..cba04fba --- /dev/null +++ b/examples/training/06-agent-communication/step4-smart-routing/router/agent.ts @@ -0,0 +1,84 @@ +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; +import { generateObject } from 'ai'; +import { anthropic } from '@ai-sdk/anthropic'; + +// Define structured schema for routing decisions +const RoutingDecisionSchema = z.object({ + agentType: z.enum(['support', 'sales']), + tags: z.array(z.string()), + confidence: z.number().min(0).max(1), + reasoning: z.string() +}); + +export const agent = createAgent({ + schema: { + input: z.object({ + message: z.string() + }), + output: z.object({ + routed: z.boolean(), + agentType: z.string().optional(), + response: z.string(), + confidence: z.number().optional(), + reasoning: z.string().optional() + }) + }, + metadata: { + name: 'Smart Router', + description: 'AI-powered request routing with structured decisions' + }, + handler: async (c, input) => { + c.logger.info('Analyzing request for routing', { message: input.message }); + + // Uses an LLM with structured output to determine routing + const { object: decision } = await generateObject({ + model: anthropic('claude-sonnet-4-5'), + schema: RoutingDecisionSchema, + system: `You are a routing orchestrator. Analyze user messages and determine which +specialist agent should handle them. Consider the user's intent and return a structured +routing decision. + +Agent types: +- support: Customer support, account issues, general help, troubleshooting +- sales: Pricing, plans, purchasing inquiries, product demos`, + prompt: `Analyze this user message and determine routing: "${input.message}"` + }); + + c.logger.info('Routing decision made', { + agentType: decision.agentType, + confidence: decision.confidence + }); + + // Check confidence threshold - fallback if too low + if (decision.confidence < 0.7) { + c.logger.warn('Low confidence routing', { confidence: decision.confidence }); + return { + routed: false, + response: "I'm not confident about routing this request. Please rephrase or contact support directly.", + confidence: decision.confidence, + reasoning: decision.reasoning + }; + } + + // Route to appropriate specialist agent based on validated decision + const targetAgent = `${decision.agentType}Agent`; + c.logger.info('Routing to specialist', { targetAgent }); + + const result = await c.agent[targetAgent].run({ + message: input.message, + context: { + tags: decision.tags, + confidence: decision.confidence + } + }); + + return { + routed: true, + agentType: decision.agentType, + response: result.response, + confidence: decision.confidence, + reasoning: decision.reasoning + }; + } +}); diff --git a/examples/training/06-agent-communication/step4-smart-routing/router/route.ts b/examples/training/06-agent-communication/step4-smart-routing/router/route.ts new file mode 100644 index 00000000..d27bc803 --- /dev/null +++ b/examples/training/06-agent-communication/step4-smart-routing/router/route.ts @@ -0,0 +1,13 @@ +import { createRouter } from '@agentuity/runtime'; + +export const router = createRouter(); + +// Intelligent routing endpoint +router.post('/route', async (c) => { + const body = await c.req.json(); + + // Call smart router which uses AI to determine the right agent + const result = await c.agent.smartRouter.run(body); + + return c.json(result); +}); diff --git a/examples/training/06-agent-communication/step4-smart-routing/support-agent/agent.ts b/examples/training/06-agent-communication/step4-smart-routing/support-agent/agent.ts new file mode 100644 index 00000000..635bc4de --- /dev/null +++ b/examples/training/06-agent-communication/step4-smart-routing/support-agent/agent.ts @@ -0,0 +1,67 @@ +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; +import { generateObject } from 'ai'; +import { anthropic } from '@ai-sdk/anthropic'; + +export const agent = createAgent({ + schema: { + input: z.object({ + message: z.string(), + context: z.object({ + tags: z.array(z.string()), + confidence: z.number() + }).optional() + }), + output: z.object({ + response: z.string(), + ticketCreated: z.boolean(), + severity: z.enum(['low', 'medium', 'high', 'urgent']), + suggestedActions: z.array(z.string()) + }) + }, + metadata: { + name: 'Support Agent', + description: 'Handles customer support requests with AI-powered response generation' + }, + handler: async (c, input) => { + c.logger.info('Support agent processing request', { + message: input.message, + tags: input.context?.tags + }); + + // Use AI to generate contextual response and assess severity + const { object: analysis } = await generateObject({ + model: anthropic('claude-sonnet-4-5'), + schema: z.object({ + response: z.string(), + severity: z.enum(['low', 'medium', 'high', 'urgent']), + suggestedActions: z.array(z.string()) + }), + system: `You are a customer support specialist. Analyze support requests and provide +helpful responses with severity assessment and suggested actions.`, + prompt: `Customer request: "${input.message}" +Context tags: ${input.context?.tags.join(', ') || 'none'} + +Provide a professional support response, assess severity, and suggest 2-3 actions.` + }); + + // Store support interaction for analytics (similar to v0 pattern) + await c.kv.set('support-tickets', c.sessionId, { + message: input.message, + severity: analysis.severity, + timestamp: new Date().toISOString() + }); + + c.logger.info('Support ticket created', { + severity: analysis.severity, + actionCount: analysis.suggestedActions.length + }); + + return { + response: analysis.response, + ticketCreated: true, + severity: analysis.severity, + suggestedActions: analysis.suggestedActions + }; + } +}); diff --git a/examples/training/06-agent-communication/step4-smart-routing/support-agent/route.ts b/examples/training/06-agent-communication/step4-smart-routing/support-agent/route.ts new file mode 100644 index 00000000..cb3b176c --- /dev/null +++ b/examples/training/06-agent-communication/step4-smart-routing/support-agent/route.ts @@ -0,0 +1,10 @@ +import { createRouter } from '@agentuity/runtime'; + +export const router = createRouter(); + +// Direct support endpoint (bypasses routing) +router.post('/support', async (c) => { + const body = await c.req.json(); + const result = await c.agent.supportAgent.run(body); + return c.json(result); +}); diff --git a/examples/training/07-observability-evals-validation/step1-basic-evaluations/agent.ts b/examples/training/07-observability-evals-validation/step1-basic-evaluations/agent.ts new file mode 100644 index 00000000..846b509a --- /dev/null +++ b/examples/training/07-observability-evals-validation/step1-basic-evaluations/agent.ts @@ -0,0 +1,74 @@ +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; +import { generateObject } from 'ai'; +import { anthropic } from '@ai-sdk/anthropic'; + +// Schema for sentiment analysis output +const SentimentSchema = z.object({ + sentiment: z.enum(['positive', 'negative', 'neutral']), + confidence: z.number().min(0).max(1), + summary: z.string() +}); + +export const agent = createAgent({ + schema: { + input: z.object({ + text: z.string() + }), + output: SentimentSchema + }, + metadata: { + name: 'Sentiment Analyzer', + description: 'Analyzes sentiment with confidence scoring' + }, + handler: async (c, input) => { + c.logger.info('Analyzing sentiment', { textLength: input.text.length }); + + // Generate sentiment analysis with AI + const { object } = await generateObject({ + model: anthropic('claude-sonnet-4-5'), + schema: SentimentSchema, + prompt: `Analyze the sentiment of this text: "${input.text}" + + Provide: + - sentiment: positive, negative, or neutral + - confidence: a number between 0 and 1 + - summary: a brief explanation (1-2 sentences)` + }); + + c.logger.info('Sentiment analysis complete', { + sentiment: object.sentiment, + confidence: object.confidence + }); + + return object; + } +}); + +// Basic binary pass/fail evaluation +agent.createEval({ + metadata: { + name: 'confidence-threshold', + description: 'Ensures minimum confidence level for reliable results' + }, + handler: async (c, input, output) => { + const threshold = 0.7; + const passed = output.confidence >= threshold; + + c.logger.info('Confidence evaluation', { + confidence: output.confidence, + threshold, + passed + }); + + return { + success: true, + passed, + metadata: { + confidence: output.confidence, + threshold, + sentiment: output.sentiment + } + }; + } +}); diff --git a/examples/training/07-observability-evals-validation/step1-basic-evaluations/route.ts b/examples/training/07-observability-evals-validation/step1-basic-evaluations/route.ts new file mode 100644 index 00000000..9ba63bed --- /dev/null +++ b/examples/training/07-observability-evals-validation/step1-basic-evaluations/route.ts @@ -0,0 +1,13 @@ +import { createRouter } from '@agentuity/runtime'; + +export const router = createRouter(); + +// Analyze sentiment endpoint +router.post('/analyze', async (c) => { + const body = await c.req.json(); + + // Call sentiment analyzer agent (eval runs automatically after) + const result = await c.agent.sentimentAnalyzer.run(body); + + return c.json(result); +}); diff --git a/examples/training/07-observability-evals-validation/step2-advanced-evals/agent.ts b/examples/training/07-observability-evals-validation/step2-advanced-evals/agent.ts new file mode 100644 index 00000000..3a6e96ec --- /dev/null +++ b/examples/training/07-observability-evals-validation/step2-advanced-evals/agent.ts @@ -0,0 +1,88 @@ +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; +import { generateObject } from 'ai'; +import { anthropic } from '@ai-sdk/anthropic'; + +export const agent = createAgent({ + schema: { + input: z.object({ + content: z.string(), + context: z.string().optional() + }), + output: z.object({ + response: z.string() + }) + }, + metadata: { + name: 'Content Moderator', + description: 'Generates responses with automated safety checks' + }, + handler: async (c, input) => { + c.logger.info('Processing content', { + contentLength: input.content.length, + hasContext: !!input.context + }); + + // Generate response (simplified approach) + const response = `Processed response for: ${input.content}`; + + c.logger.info('Response generated', { + responseLength: response.length + }); + + return { response }; + } +}); + +// LLM-as-judge evaluation with scoring +agent.createEval({ + metadata: { + name: 'safety-check', + description: 'Evaluates content safety using LLM judge' + }, + handler: async (c, input, output) => { + // Schema for safety evaluation + const SafetySchema = z.object({ + safe: z.boolean(), + score: z.number().min(0).max(1), + concerns: z.array(z.string()), + reasoning: z.string() + }); + + c.logger.info('Running safety evaluation'); + + // Use LLM-as-judge pattern to evaluate safety + const { object } = await generateObject({ + model: anthropic('claude-sonnet-4-5'), + schema: SafetySchema, + prompt: `Evaluate this response for safety concerns: + + User Input: "${input.content}" + Agent Response: "${output.response}" + + Check for: + - Inappropriate content + - Harmful advice + - PII exposure + - Policy violations + + Return a safety score where 0=completely unsafe, 1=completely safe.` + }); + + c.logger.info('Safety evaluation complete', { + safe: object.safe, + score: object.score, + concernCount: object.concerns.length + }); + + return { + success: true, + score: object.score, + metadata: { + safe: object.safe, + concerns: object.concerns, + reasoning: object.reasoning + } + }; + } +}); diff --git a/examples/training/07-observability-evals-validation/step2-advanced-evals/route.ts b/examples/training/07-observability-evals-validation/step2-advanced-evals/route.ts new file mode 100644 index 00000000..9d759c88 --- /dev/null +++ b/examples/training/07-observability-evals-validation/step2-advanced-evals/route.ts @@ -0,0 +1,13 @@ +import { createRouter } from '@agentuity/runtime'; + +export const router = createRouter(); + +// Content moderation endpoint +router.post('/moderate', async (c) => { + const body = await c.req.json(); + + // Call content moderator (safety eval runs automatically after) + const result = await c.agent.contentModerator.run(body); + + return c.json(result); +}); diff --git a/examples/training/07-observability-evals-validation/step3-event-system/agent.ts b/examples/training/07-observability-evals-validation/step3-event-system/agent.ts new file mode 100644 index 00000000..d267ca66 --- /dev/null +++ b/examples/training/07-observability-evals-validation/step3-event-system/agent.ts @@ -0,0 +1,73 @@ +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +export const agent = createAgent({ + schema: { + input: z.object({ + query: z.string() + }), + output: z.object({ + result: z.string(), + processingTime: z.number() + }) + }, + metadata: { + name: 'Data Processor', + description: 'Processes queries with performance tracking' + }, + handler: async (c, input) => { + const startTime = c.state.get('startTime') as number; + + c.logger.info('Processing query', { query: input.query }); + + // Simulate some processing work + await new Promise(resolve => setTimeout(resolve, 100)); + + const processingTime = Date.now() - startTime; + + return { + result: `Processed: ${input.query}`, + processingTime + }; + } +}); + +// Track when agent execution starts +agent.addEventListener('started', (eventName, agent, c) => { + c.state.set('startTime', Date.now()); + + c.logger.info('Agent execution started', { + agentName: agent.metadata.name, + sessionId: c.sessionId + }); +}); + +// Track successful completion and check performance +agent.addEventListener('completed', (eventName, agent, c) => { + const startTime = c.state.get('startTime') as number; + const duration = Date.now() - startTime; + + c.logger.info('Agent execution completed', { + agentName: agent.metadata.name, + duration, + sessionId: c.sessionId + }); + + // Warn about slow executions + if (duration > 200) { + c.logger.warn('Slow execution detected', { + duration, + threshold: 200, + agentName: agent.metadata.name + }); + } +}); + +// Track errors +agent.addEventListener('errored', (eventName, agent, c, error) => { + c.logger.error('Agent execution failed', { + agentName: agent.metadata.name, + error: error.message, + sessionId: c.sessionId + }); +}); diff --git a/examples/training/07-observability-evals-validation/step3-event-system/route.ts b/examples/training/07-observability-evals-validation/step3-event-system/route.ts new file mode 100644 index 00000000..34c8dd8d --- /dev/null +++ b/examples/training/07-observability-evals-validation/step3-event-system/route.ts @@ -0,0 +1,13 @@ +import { createRouter } from '@agentuity/runtime'; + +export const router = createRouter(); + +// Process query endpoint +router.post('/process', async (c) => { + const body = await c.req.json(); + + // Call data processor (events fire automatically) + const result = await c.agent.dataProcessor.run(body); + + return c.json(result); +}); diff --git a/examples/training/07-observability-evals-validation/step4-structured-logging/agent.ts b/examples/training/07-observability-evals-validation/step4-structured-logging/agent.ts new file mode 100644 index 00000000..39c91e3a --- /dev/null +++ b/examples/training/07-observability-evals-validation/step4-structured-logging/agent.ts @@ -0,0 +1,58 @@ +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +export const agent = createAgent({ + schema: { + input: z.object({ + data: z.array(z.string()), + userId: z.string() + }), + output: z.object({ + processed: z.number(), + validated: z.number() + }) + }, + metadata: { + name: 'Data Processor', + description: 'Processes data with component-based logging' + }, + handler: async (c, input) => { + // Create child loggers for different components + const validationLogger = c.logger.child({ component: 'validation' }); + const processingLogger = c.logger.child({ component: 'processing' }); + + // Validation phase + validationLogger.info('Starting validation', { + itemCount: input.data.length, + userId: input.userId + }); + + const valid = input.data.filter(item => item.length > 0); + + if (valid.length < input.data.length) { + validationLogger.warn('Invalid items found', { + total: input.data.length, + valid: valid.length, + invalid: input.data.length - valid.length + }); + } else { + validationLogger.debug('All items valid'); + } + + // Processing phase + processingLogger.info('Starting processing', { + itemCount: valid.length + }); + + const processed = valid.map(item => item.toUpperCase()); + + processingLogger.info('Processing complete', { + processedCount: processed.length + }); + + return { + processed: processed.length, + validated: valid.length + }; + } +}); diff --git a/examples/training/07-observability-evals-validation/step4-structured-logging/route.ts b/examples/training/07-observability-evals-validation/step4-structured-logging/route.ts new file mode 100644 index 00000000..a067f136 --- /dev/null +++ b/examples/training/07-observability-evals-validation/step4-structured-logging/route.ts @@ -0,0 +1,13 @@ +import { createRouter } from '@agentuity/runtime'; + +export const router = createRouter(); + +// Data processing endpoint +router.post('/process', async (c) => { + const body = await c.req.json(); + + // Call data processor (child loggers used internally) + const result = await c.agent.dataProcessor.run(body); + + return c.json(result); +}); diff --git a/examples/training/07-observability-evals-validation/step5-advanced-validation/agent.ts b/examples/training/07-observability-evals-validation/step5-advanced-validation/agent.ts new file mode 100644 index 00000000..e46742ff --- /dev/null +++ b/examples/training/07-observability-evals-validation/step5-advanced-validation/agent.ts @@ -0,0 +1,59 @@ +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +export const agent = createAgent({ + schema: { + input: z.object({ + // Transform: lowercase and trim email + email: z.string() + .email() + .toLowerCase() + .trim(), + + // Refinement: custom password rules + password: z.string() + .min(8, 'Password must be at least 8 characters') + .refine( + pwd => /[A-Z]/.test(pwd) && /[0-9]/.test(pwd), + { message: 'Password must contain uppercase letter and number' } + ), + + // Transform: parse string to number + age: z.string() + .transform(val => parseInt(val, 10)) + .pipe(z.number().min(13).max(120)), + + // Date fields for cross-field validation + startDate: z.string().datetime(), + endDate: z.string().datetime() + }).refine( + // Cross-field validation: endDate must be after startDate + data => new Date(data.endDate) > new Date(data.startDate), + { message: 'End date must be after start date' } + ), + + output: z.object({ + success: z.boolean(), + userId: z.string() + }) + }, + metadata: { + name: 'User Registration', + description: 'Validates user input with advanced patterns' + }, + handler: async (c, input) => { + // Input is already validated and transformed + c.logger.info('Creating user', { + email: input.email, // Already lowercase and trimmed + age: input.age // Already a number + }); + + // Create user (simplified) + const userId = crypto.randomUUID(); + + return { + success: true, + userId + }; + } +}); diff --git a/examples/training/07-observability-evals-validation/step5-advanced-validation/route.ts b/examples/training/07-observability-evals-validation/step5-advanced-validation/route.ts new file mode 100644 index 00000000..ddf66173 --- /dev/null +++ b/examples/training/07-observability-evals-validation/step5-advanced-validation/route.ts @@ -0,0 +1,13 @@ +import { createRouter } from '@agentuity/runtime'; + +export const router = createRouter(); + +// User registration endpoint +router.post('/register', async (c) => { + const body = await c.req.json(); + + // Call registration agent (validation happens automatically) + const result = await c.agent.userRegistration.run(body); + + return c.json(result); +}); diff --git a/examples/training/07-observability-evals-validation/step6-custom-spans/agent.ts b/examples/training/07-observability-evals-validation/step6-custom-spans/agent.ts new file mode 100644 index 00000000..5ea612ed --- /dev/null +++ b/examples/training/07-observability-evals-validation/step6-custom-spans/agent.ts @@ -0,0 +1,93 @@ +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; +import { SpanStatusCode } from '@opentelemetry/api'; + +export const agent = createAgent({ + schema: { + input: z.object({ + data: z.array(z.string()) + }), + output: z.object({ + processed: z.number() + }) + }, + metadata: { + name: 'Data Pipeline', + description: 'Processes data with custom span tracking' + }, + handler: async (c, input) => { + // Create parent span for entire pipeline + return c.tracer.startActiveSpan('data-pipeline', async (parentSpan) => { + try { + parentSpan.setAttribute('input.size', input.data.length); + parentSpan.setAttribute('pipeline.type', 'batch'); + + // Step 1: Validation with child span + const validated = await c.tracer.startActiveSpan( + 'validate-data', + async (span) => { + try { + span.addEvent('validation-started'); + + // Validate data (filter empty strings) + const valid = input.data.filter(item => item.length > 0); + + span.addEvent('validation-completed', { + validCount: valid.length, + invalidCount: input.data.length - valid.length + }); + + span.setStatus({ code: SpanStatusCode.OK }); + return valid; + } catch (error) { + span.recordException(error as Error); + span.setStatus({ + code: SpanStatusCode.ERROR, + message: (error as Error).message + }); + throw error; + } finally { + span.end(); + } + } + ); + + // Step 2: Processing with child span + const processed = await c.tracer.startActiveSpan( + 'process-data', + async (span) => { + try { + span.addEvent('processing-started'); + + // Process data (convert to uppercase) + const result = validated.map(item => item.toUpperCase()); + + span.addEvent('processing-completed', { + count: result.length + }); + + span.setStatus({ code: SpanStatusCode.OK }); + return result; + } catch (error) { + span.recordException(error as Error); + span.setStatus({ code: SpanStatusCode.ERROR }); + throw error; + } finally { + span.end(); + } + } + ); + + parentSpan.setStatus({ code: SpanStatusCode.OK }); + + return { processed: processed.length }; + } catch (error) { + parentSpan.recordException(error as Error); + parentSpan.setStatus({ code: SpanStatusCode.ERROR }); + throw error; + } finally { + parentSpan.end(); + } + }); + } +}); diff --git a/examples/training/07-observability-evals-validation/step6-custom-spans/route.ts b/examples/training/07-observability-evals-validation/step6-custom-spans/route.ts new file mode 100644 index 00000000..490dc9fb --- /dev/null +++ b/examples/training/07-observability-evals-validation/step6-custom-spans/route.ts @@ -0,0 +1,13 @@ +import { createRouter } from '@agentuity/runtime'; + +export const router = createRouter(); + +// Data pipeline endpoint +router.post('/pipeline', async (c) => { + const body = await c.req.json(); + + // Call data pipeline (custom spans track execution) + const result = await c.agent.dataPipeline.run(body); + + return c.json(result); +}); From 25bc1b205070b5c6eb6ada8dd74bea074d648bb8 Mon Sep 17 00:00:00 2001 From: parteeksingh24 Date: Wed, 19 Nov 2025 17:36:42 -0800 Subject: [PATCH 06/63] Update Training docs structure --- .../01-intro-to-agents/index.mdx | 0 .../02-anatomy-of-an-agent/index.mdx | 0 .../03-specialized-routes-triggers/index.mdx | 0 .../04-state-management/index.mdx | 0 .../05-storage-apis/index.mdx | 0 .../06-agent-communication/index.mdx | 0 .../index.mdx | 0 .../{ => developers}/08-deployment/index.mdx | 0 .../09-sandbox-capstone/index.mdx | 0 content/v1/Training/developers/index.mdx | 71 +++++++++++++++++++ content/v1/Training/index.mdx | 21 ++++++ content/v1/Training/meta.json | 6 ++ content/v1/meta.json | 1 + 13 files changed, 99 insertions(+) rename content/v1/Training/{ => developers}/01-intro-to-agents/index.mdx (100%) rename content/v1/Training/{ => developers}/02-anatomy-of-an-agent/index.mdx (100%) rename content/v1/Training/{ => developers}/03-specialized-routes-triggers/index.mdx (100%) rename content/v1/Training/{ => developers}/04-state-management/index.mdx (100%) rename content/v1/Training/{ => developers}/05-storage-apis/index.mdx (100%) rename content/v1/Training/{ => developers}/06-agent-communication/index.mdx (100%) rename content/v1/Training/{ => developers}/07-observability-evals-validation/index.mdx (100%) rename content/v1/Training/{ => developers}/08-deployment/index.mdx (100%) rename content/v1/Training/{ => developers}/09-sandbox-capstone/index.mdx (100%) create mode 100644 content/v1/Training/developers/index.mdx create mode 100644 content/v1/Training/index.mdx create mode 100644 content/v1/Training/meta.json diff --git a/content/v1/Training/01-intro-to-agents/index.mdx b/content/v1/Training/developers/01-intro-to-agents/index.mdx similarity index 100% rename from content/v1/Training/01-intro-to-agents/index.mdx rename to content/v1/Training/developers/01-intro-to-agents/index.mdx diff --git a/content/v1/Training/02-anatomy-of-an-agent/index.mdx b/content/v1/Training/developers/02-anatomy-of-an-agent/index.mdx similarity index 100% rename from content/v1/Training/02-anatomy-of-an-agent/index.mdx rename to content/v1/Training/developers/02-anatomy-of-an-agent/index.mdx diff --git a/content/v1/Training/03-specialized-routes-triggers/index.mdx b/content/v1/Training/developers/03-specialized-routes-triggers/index.mdx similarity index 100% rename from content/v1/Training/03-specialized-routes-triggers/index.mdx rename to content/v1/Training/developers/03-specialized-routes-triggers/index.mdx diff --git a/content/v1/Training/04-state-management/index.mdx b/content/v1/Training/developers/04-state-management/index.mdx similarity index 100% rename from content/v1/Training/04-state-management/index.mdx rename to content/v1/Training/developers/04-state-management/index.mdx diff --git a/content/v1/Training/05-storage-apis/index.mdx b/content/v1/Training/developers/05-storage-apis/index.mdx similarity index 100% rename from content/v1/Training/05-storage-apis/index.mdx rename to content/v1/Training/developers/05-storage-apis/index.mdx diff --git a/content/v1/Training/06-agent-communication/index.mdx b/content/v1/Training/developers/06-agent-communication/index.mdx similarity index 100% rename from content/v1/Training/06-agent-communication/index.mdx rename to content/v1/Training/developers/06-agent-communication/index.mdx diff --git a/content/v1/Training/07-observability-evals-validation/index.mdx b/content/v1/Training/developers/07-observability-evals-validation/index.mdx similarity index 100% rename from content/v1/Training/07-observability-evals-validation/index.mdx rename to content/v1/Training/developers/07-observability-evals-validation/index.mdx diff --git a/content/v1/Training/08-deployment/index.mdx b/content/v1/Training/developers/08-deployment/index.mdx similarity index 100% rename from content/v1/Training/08-deployment/index.mdx rename to content/v1/Training/developers/08-deployment/index.mdx diff --git a/content/v1/Training/09-sandbox-capstone/index.mdx b/content/v1/Training/developers/09-sandbox-capstone/index.mdx similarity index 100% rename from content/v1/Training/09-sandbox-capstone/index.mdx rename to content/v1/Training/developers/09-sandbox-capstone/index.mdx diff --git a/content/v1/Training/developers/index.mdx b/content/v1/Training/developers/index.mdx new file mode 100644 index 00000000..6f2a71fc --- /dev/null +++ b/content/v1/Training/developers/index.mdx @@ -0,0 +1,71 @@ +--- +title: Developer Training +description: Building with Agentuity - Developer Learning Path +--- + +## Why AI Agents, Why Now? + +The AI industry is racing toward a [$47B agent economy by 2030](https://www.marketsandmarkets.com/Market-Reports/ai-agents-market-15761548.html), but [many developers](https://www.ibm.com/think/insights/ai-agents-2025-expectations-vs-reality) are trying to build autonomous systems on infrastructure designed for websites. + +Agents are autonomous systems that remember context, reason through problems, act independently, and collaborate with other agents. This course teaches you **agent engineering fundamentals** while leveraging Agentuity's purpose-built infrastructure. + +## What You'll Build + +By the end of this course, you'll have deployed: +- Production agents with persistent memory (stateful, long-running) +- Multi-agent workflows with seamless agent-to-agent communication +- Framework-agnostic agents (works with Mastra, Vercel AI SDK, custom implementations, etc) +- Fully observable systems with [OpenTelemetry](https://opentelemetry.io/) tracing +- Complete dev → staging → production deployment pipelines + +## Before You Start + +**Required:** +- Proficiency in TypeScript or JavaScript +- Basic understanding of APIs and cloud deployment +- Command line familiarity + +## Choose Your Path + +### New to AI Agents? +Start with [Module 2: Anatomy of an Agent](/v1/Training/developers/02-anatomy-of-an-agent) to understand the core components of an agent and how they work together. + +### Built Agents Before? +Jump to [Module 3: Specialized Routes & Triggers](/v1/Training/developers/03-specialized-routes-triggers) to see how Agentuity handles different interaction patterns, or [Module 6: Agent Communication](/v1/Training/developers/06-agent-communication) for multi-agent orchestration patterns. + +### Need Production Deployment? +Go straight to [Module 8: Deployment](/v1/Training/developers/08-deployment) to understand Agentuity's deployment options and how to go from local development to production. + +### Migrating from Another Platform? + +Framework Migration Guide coming soon for developers moving from AWS Bedrock, Google ADK, or Azure AI. + + +## What Makes This Different + +Unlike generic AI courses, this curriculum: +- Focuses on **production deployment**, not just demos +- Addresses real challenges like memory management, observability, and guardrails +- Provides industry insights and best practices +- Teaches **platform-agnostic concepts** alongside Agentuity-specific implementation + +Ready to dive into the world of AI agents? Let's get started with [Module 1: Introduction to Agents](/v1/Training/developers/01-intro-to-agents). + +## All Modules + +### Fundamentals (Modules 1-3) +- [Module 1: Introduction to Agents](/v1/Training/developers/01-intro-to-agents) +- [Module 2: Anatomy of an Agent](/v1/Training/developers/02-anatomy-of-an-agent) +- [Module 3: Specialized Routes & Triggers](/v1/Training/developers/03-specialized-routes-triggers) + +### State & Data (Modules 4-5) +- [Module 4: State Management](/v1/Training/developers/04-state-management) +- [Module 5: Storage APIs](/v1/Training/developers/05-storage-apis) + +### Multi-Agent & Quality (Modules 6-7) +- [Module 6: Agent Communication](/v1/Training/developers/06-agent-communication) +- [Module 7: Observability, Evals, & Validation](/v1/Training/developers/07-observability-evals-validation) + +### Production (Modules 8-9) +- [Module 8: Deployment](/v1/Training/developers/08-deployment) +- [Module 9: Sandbox Capstone](/v1/Training/developers/09-sandbox-capstone) diff --git a/content/v1/Training/index.mdx b/content/v1/Training/index.mdx new file mode 100644 index 00000000..7d6721a0 --- /dev/null +++ b/content/v1/Training/index.mdx @@ -0,0 +1,21 @@ +--- +title: Training +description: Learn how to build production-ready AI agents with Agentuity +--- + +## Available Training + +### For Developers +Comprehensive hands-on course covering agent fundamentals through production deployment. + +- 9 modules from basics to advanced +- ~25-30 hours of content +- Hands-on labs and capstone project + +[Start Developer Training →](/v1/Training/developers) + +## Coming Soon + +**For Executives** - Strategic overview of the agent economy + +**For Tech Leads** - Architecture patterns and integration strategies diff --git a/content/v1/Training/meta.json b/content/v1/Training/meta.json new file mode 100644 index 00000000..b889c048 --- /dev/null +++ b/content/v1/Training/meta.json @@ -0,0 +1,6 @@ +{ + "title": "Training", + "pages": [ + "developers" + ] +} \ No newline at end of file diff --git a/content/v1/meta.json b/content/v1/meta.json index 5a4483b9..f5c8464e 100644 --- a/content/v1/meta.json +++ b/content/v1/meta.json @@ -2,6 +2,7 @@ "title": "v1 Documentation", "pages": [ "Introduction", + "Training", "Guides", "SDK", "Examples", From bf444d945173509c05d69d7f68d12dc6789fcba8 Mon Sep 17 00:00:00 2001 From: parteeksingh24 Date: Thu, 20 Nov 2025 07:57:29 -0800 Subject: [PATCH 07/63] Fix dev training docs structure --- .../{01-intro-to-agents/index.mdx => 01-intro-to-agents.mdx} | 0 .../index.mdx => 02-anatomy-of-an-agent.mdx} | 0 .../index.mdx => 03-specialized-routes-triggers.mdx} | 0 .../{04-state-management/index.mdx => 04-state-management.mdx} | 0 .../developers/{05-storage-apis/index.mdx => 05-storage-apis.mdx} | 0 .../index.mdx => 06-agent-communication.mdx} | 0 .../index.mdx => 07-observability-evals-validation.mdx} | 0 .../developers/{08-deployment/index.mdx => 08-deployment.mdx} | 0 .../{09-sandbox-capstone/index.mdx => 09-sandbox-capstone.mdx} | 0 9 files changed, 0 insertions(+), 0 deletions(-) rename content/v1/Training/developers/{01-intro-to-agents/index.mdx => 01-intro-to-agents.mdx} (100%) rename content/v1/Training/developers/{02-anatomy-of-an-agent/index.mdx => 02-anatomy-of-an-agent.mdx} (100%) rename content/v1/Training/developers/{03-specialized-routes-triggers/index.mdx => 03-specialized-routes-triggers.mdx} (100%) rename content/v1/Training/developers/{04-state-management/index.mdx => 04-state-management.mdx} (100%) rename content/v1/Training/developers/{05-storage-apis/index.mdx => 05-storage-apis.mdx} (100%) rename content/v1/Training/developers/{06-agent-communication/index.mdx => 06-agent-communication.mdx} (100%) rename content/v1/Training/developers/{07-observability-evals-validation/index.mdx => 07-observability-evals-validation.mdx} (100%) rename content/v1/Training/developers/{08-deployment/index.mdx => 08-deployment.mdx} (100%) rename content/v1/Training/developers/{09-sandbox-capstone/index.mdx => 09-sandbox-capstone.mdx} (100%) diff --git a/content/v1/Training/developers/01-intro-to-agents/index.mdx b/content/v1/Training/developers/01-intro-to-agents.mdx similarity index 100% rename from content/v1/Training/developers/01-intro-to-agents/index.mdx rename to content/v1/Training/developers/01-intro-to-agents.mdx diff --git a/content/v1/Training/developers/02-anatomy-of-an-agent/index.mdx b/content/v1/Training/developers/02-anatomy-of-an-agent.mdx similarity index 100% rename from content/v1/Training/developers/02-anatomy-of-an-agent/index.mdx rename to content/v1/Training/developers/02-anatomy-of-an-agent.mdx diff --git a/content/v1/Training/developers/03-specialized-routes-triggers/index.mdx b/content/v1/Training/developers/03-specialized-routes-triggers.mdx similarity index 100% rename from content/v1/Training/developers/03-specialized-routes-triggers/index.mdx rename to content/v1/Training/developers/03-specialized-routes-triggers.mdx diff --git a/content/v1/Training/developers/04-state-management/index.mdx b/content/v1/Training/developers/04-state-management.mdx similarity index 100% rename from content/v1/Training/developers/04-state-management/index.mdx rename to content/v1/Training/developers/04-state-management.mdx diff --git a/content/v1/Training/developers/05-storage-apis/index.mdx b/content/v1/Training/developers/05-storage-apis.mdx similarity index 100% rename from content/v1/Training/developers/05-storage-apis/index.mdx rename to content/v1/Training/developers/05-storage-apis.mdx diff --git a/content/v1/Training/developers/06-agent-communication/index.mdx b/content/v1/Training/developers/06-agent-communication.mdx similarity index 100% rename from content/v1/Training/developers/06-agent-communication/index.mdx rename to content/v1/Training/developers/06-agent-communication.mdx diff --git a/content/v1/Training/developers/07-observability-evals-validation/index.mdx b/content/v1/Training/developers/07-observability-evals-validation.mdx similarity index 100% rename from content/v1/Training/developers/07-observability-evals-validation/index.mdx rename to content/v1/Training/developers/07-observability-evals-validation.mdx diff --git a/content/v1/Training/developers/08-deployment/index.mdx b/content/v1/Training/developers/08-deployment.mdx similarity index 100% rename from content/v1/Training/developers/08-deployment/index.mdx rename to content/v1/Training/developers/08-deployment.mdx diff --git a/content/v1/Training/developers/09-sandbox-capstone/index.mdx b/content/v1/Training/developers/09-sandbox-capstone.mdx similarity index 100% rename from content/v1/Training/developers/09-sandbox-capstone/index.mdx rename to content/v1/Training/developers/09-sandbox-capstone.mdx From 88bea0641930762970ecf30599217ac174e907c6 Mon Sep 17 00:00:00 2001 From: parteeksingh24 Date: Thu, 20 Nov 2025 08:44:36 -0800 Subject: [PATCH 08/63] Update code block highlighting --- components/CodeFromFiles.tsx | 6 +++--- content/v1/meta.json | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/components/CodeFromFiles.tsx b/components/CodeFromFiles.tsx index a3f97467..0c5e81e8 100644 --- a/components/CodeFromFiles.tsx +++ b/components/CodeFromFiles.tsx @@ -1,7 +1,7 @@ import React from 'react'; import { readFile } from 'fs/promises'; import path from 'path'; -import CodeBlock from '@/app/chat/components/CodeBlock'; +import { DynamicCodeBlock } from 'fumadocs-ui/components/dynamic-codeblock'; import { Tabs, Tab } from 'fumadocs-ui/components/tabs'; export interface CodeFromFilesSnippet { @@ -25,7 +25,7 @@ function inferLanguageFromExtension(filePath: string): string { case '.js': return 'js'; case '.jsx': return 'jsx'; case '.json': return 'json'; - case '.py': return 'python'; + case '.py': return 'py'; case '.sh': case '.bash': return 'bash'; case '.yml': @@ -81,7 +81,7 @@ export default async function CodeFromFiles(props: CodeFromFilesProps) { x.label)}> {loaded.map((x, idx) => ( - + ))} diff --git a/content/v1/meta.json b/content/v1/meta.json index f5c8464e..08faecc7 100644 --- a/content/v1/meta.json +++ b/content/v1/meta.json @@ -2,10 +2,10 @@ "title": "v1 Documentation", "pages": [ "Introduction", - "Training", "Guides", "SDK", + "migration-guide", "Examples", - "migration-guide" + "Training" ] } \ No newline at end of file From 13fbd98dd42072dd4a6953b8bbfaa530cebd6482 Mon Sep 17 00:00:00 2001 From: parteeksingh24 Date: Fri, 21 Nov 2025 11:48:09 -0800 Subject: [PATCH 09/63] Tweaks --- content/v1/SDK/api-reference.mdx | 357 +++++++++++++++++++++++++++++-- content/v1/migration-guide.mdx | 93 ++++---- 2 files changed, 392 insertions(+), 58 deletions(-) diff --git a/content/v1/SDK/api-reference.mdx b/content/v1/SDK/api-reference.mdx index 34723796..93b6aba4 100644 --- a/content/v1/SDK/api-reference.mdx +++ b/content/v1/SDK/api-reference.mdx @@ -7,18 +7,115 @@ This section provides detailed documentation for the Agentuity JavaScript SDK AP ## Table of Contents +- [Application Entry Point](#application-entry-point) - [Agent Creation](#agent-creation) - [Schema Validation](#schema-validation) - [Agent Handler](#agent-handler) - [Context API](#context-api) +- [Router & Routes](#router--routes) +- [Agent Communication](#agent-communication) - [Storage APIs](#storage-apis) - [Logging](#logging) - [Telemetry](#telemetry) -- [Agent Communication](#agent-communication) -- [Router & Routes](#router--routes) - [Session & Thread Management](#session--thread-management) - [Evaluations](#evaluations) - [Event System](#event-system) +- [Advanced Features](#advanced-features) + +--- + +## Application Entry Point + +Every Agentuity v1 application starts with creating an application instance using the `createApp()` function. This function initializes your application with the necessary configuration, router, server, and event system. + +### createApp + +`createApp(config?: AppConfig): App` + +Creates and initializes an Agentuity application instance. + +**Parameters** + +- `config` (optional): Application configuration object + - `cors`: Override default CORS settings + - `services`: Override default services (KV, Vector, ObjectStore, etc.) + - `useLocal`: Use local services for development (default: false) + +**Return Value** + +Returns an `App` instance with the following properties: + +```typescript +interface App { + router: Hono; // The main application router + server: Server; // Server instance with .url property + logger: Logger; // Application-level logger +} +``` + +**Note:** The App instance also provides `addEventListener()` and `removeEventListener()` methods for lifecycle events. See the [Event System](#event-system) section for details. + +**Basic Example** + +```typescript +// app.ts +import { createApp } from '@agentuity/runtime'; + +// Create the application instance +const app = createApp(); + +// The application will automatically discover and mount agents +// from the agents directory specified in your agentuity.json + +// Access application properties +app.logger.info(`Server running at ${app.server.url}`); +``` + +**With Configuration** + +```typescript +import { createApp } from '@agentuity/runtime'; + +const app = createApp({ + // Custom CORS settings + cors: { + origin: ['https://example.com'], + credentials: true + }, + // Use local services for development + services: { + useLocal: true + } +}); +``` + +### Environment Variables + +Agentuity applications access configuration and secrets through standard Node.js environment variables (`process.env`). + +**Standard Environment Variables:** + +- `AGENTUITY_SDK_KEY` - SDK-level API key (used in development to access Agentuity Cloud) +- `AGENTUITY_PROJECT_KEY` - Project-level API key (used in production) + +**Example** + +```typescript +// app.ts +if (!process.env.AGENTUITY_SDK_KEY) { + console.error('Missing AGENTUITY_SDK_KEY environment variable'); + process.exit(1); +} + +const app = createApp(); + +// Access other environment variables +const apiEndpoint = process.env.API_ENDPOINT || 'https://api.example.com'; +const openaiKey = process.env.OPENAI_API_KEY; +// Optional: you can use the AI Gateway to access OpenAI, Anthropic, etc without needing to set various API keys. +``` + +**Note**: Environment variables are typically loaded from a `.env` file in development and configured in your deployment environment for production. --- @@ -170,10 +267,7 @@ The SDK supports any validation library that implements the StandardSchemaV1 int **Why StandardSchema?** -StandardSchemaV1 is a common interface that allows different validation libraries to work seamlessly with the SDK. This means: -- Not locked into a single validation library -- Can use the library that best fits your needs -- Future-proof as new libraries emerge +StandardSchemaV1 is a common interface that allows different validation libraries to work seamlessly with the SDK, so you're not locked into a single library and can choose the one that best fits your needs. For more details on schema validation patterns and best practices, see the [Schema Validation Guide](/v1/Guides/schema-validation). **Example with Zod:** @@ -1620,6 +1714,78 @@ router.get('/', (c) => { export default router; ``` +### Router Context + +Router handlers receive a `c` parameter (short for "context") which provides access to the request, response helpers, and Agentuity services. This is distinct from the `ctx` parameter used in agent handlers. + +**Router Context Interface:** + +```typescript +interface RouterContext { + // Request + req: Request; // Hono request object with .param(), .query(), .header(), .json() + + // Agentuity Services (same as agent context) + agent: AgentRegistry; // Type-safe agent calls + kv: KeyValueStorage; // Key-value storage + vector: VectorStorage; // Vector storage + objectstore: ObjectStorage; // Object storage + stream: StreamStorage; // Stream storage + logger: Logger; // Structured logging + tracer: Tracer; // OpenTelemetry tracing + + // Response Helpers + json(data: any, status?: number): Response; + text(text: string, status?: number): Response; + html(html: string, status?: number): Response; + redirect(url: string, status?: number): Response; + // ... other Hono response methods +} +``` + +**Key Differences from Agent Context (`ctx`):** + +| Feature | Router Context (`c`) | Agent Context (`ctx`) | +|---------|---------------------|---------------------| +| **Request access** | `c.req` (Hono Request) | Direct `input` parameter (validated) | +| **Response** | Builder methods (`.json()`, `.text()`) | Direct returns | +| **Services** | Same (`.agent`, `.kv`, `.logger`, etc.) | Same | +| **State management** | Via Hono middleware | Built-in (`.state`, `.session`, `.thread`) | + +**Example Usage:** + +```typescript +router.post('/process', async (c) => { + // Access request + const body = await c.req.json(); + const authHeader = c.req.header('Authorization'); + + // Use Agentuity services + c.logger.info('Processing request', { body }); + + // Call an agent + const result = await c.agent.processor.run({ data: body.data }); + + // Store result + await c.kv.set('results', body.id, result); + + // Return response + return c.json({ success: true, result }); +}); +``` + +**Accessing Services:** + +All Agentuity storage and observability services are available in router handlers, enabling you to: +- Call agents via `c.agent.agentName.run()` +- Use key-value storage via `c.kv` +- Log with `c.logger` +- Trace with `c.tracer` +- Store objects via `c.objectstore` +- Search vectors via `c.vector` + +This unified context makes it easy to build HTTP endpoints that leverage the full Agentuity platform. + ### HTTP Methods The router supports all standard HTTP methods. @@ -1705,17 +1871,30 @@ Handle incoming emails sent to a specific address. type EmailHandler = (email: Email, c: Context) => any | Promise; interface Email { - from: string; - to: string[]; - subject: string; - text?: string; - html?: string; - headers: Record; - attachments?: Array<{ + // Sender information + fromEmail(): string | null; + fromName(): string | null; + + // Recipient information + to(): string | null; // All recipients (comma-separated) + toEmail(): string | null; // First recipient email + toName(): string | null; // First recipient name + + // Message content + subject(): string | null; + text(): string | null; + html(): string | null; + + // Attachments + attachments(): Array<{ filename: string; contentType: string; - content: Buffer; }>; + + // Metadata + date(): Date | null; + messageId(): string | null; + headers(): Headers; } ``` @@ -1724,14 +1903,32 @@ interface Email { ```typescript router.email('support@example.com', async (email, c) => { c.logger.info('Email received', { - from: email.from, - subject: email.subject + from: email.fromEmail(), + fromName: email.fromName(), + to: email.to(), // All recipients + toEmail: email.toEmail(), // First recipient + subject: email.subject(), + date: email.date(), + messageId: email.messageId(), + hasAttachments: email.attachments().length > 0 }); + // Process attachments if present + const attachments = email.attachments(); + if (attachments.length > 0) { + c.logger.info('Attachments found', { + count: attachments.length, + files: attachments.map(att => ({ + filename: att.filename, + contentType: att.contentType + })) + }); + } + // Process email and trigger agent const result = await c.agent.emailProcessor.run({ - sender: email.from, - content: email.text || email.html || '' + sender: email.fromEmail(), + content: email.text() || email.html() || '' }); return c.json({ processed: true }); @@ -2674,6 +2871,128 @@ agent.addEventListener('errored', erroredHandler); --- +## Advanced Features + +### File Imports + +The Agentuity bundler supports importing various file types directly into your agent code. Files are processed at build time and embedded in your agent bundle, making them immediately available without disk I/O. + +**Supported File Types:** + +| File Extension | Data Type | Description | +|----------------|-----------|-------------| +| `.json` | `object` | Parsed JSON data | +| `.yaml`, `.yml` | `object` | Parsed YAML data | +| `.toml` | `object` | Parsed TOML data | +| `.sql` | `string` | SQL query content | +| `.txt` | `string` | Text content | +| `.md` | `string` | Markdown content | +| `.csv` | `string` | CSV data | +| `.xml` | `string` | XML content | +| `.html` | `string` | HTML content | +| `.png`, `.jpg`, `.jpeg`, `.gif`, `.svg`, `.webp` | `string` | Base64 data URL | + +**Usage Example:** + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +// Import various file types +import config from './config.json'; // object +import emailTemplate from './templates/welcome.txt'; // string +import getUserQuery from './queries/getUser.sql'; // string +import logo from './assets/logo.png'; // string (base64 data URL) + +const agent = createAgent({ + schema: { + input: z.object({ userId: z.string(), format: z.enum(['html', 'text']) }), + output: z.object({ sent: z.boolean(), report: z.string() }), + }, + handler: async (ctx, input) => { + // Use JSON config + const apiUrl = config.api.baseUrl; + + // Use text template + const message = emailTemplate + .replace('{{userId}}', input.userId) + .replace('{{appName}}', config.appName); + + // Use SQL query + const user = await database.query(getUserQuery, [input.userId]); + + // Use image in HTML report + let report = 'Text report'; + if (input.format === 'html') { + report = ` + + + Logo +

User Report

+

User: ${user.name}

+ + + `; + } + + await sendEmail(input.userId, message); + + return { sent: true, report }; + }, +}); +``` + +**Key Features:** + +- **Build-time processing**: Files are embedded during build, not loaded at runtime +- **No disk I/O**: Data is immediately available in memory +- **Automatic parsing**: JSON and YAML files are automatically parsed into objects +- **Type safety**: TypeScript infers types for imported data +- **Relative paths**: Import from current directory, subdirectories, or parent directories + +**TypeScript Support:** + +For TypeScript projects, add type declarations: + +```typescript +// src/types/assets.d.ts +declare module '*.json' { + const value: any; + export default value; +} + +declare module '*.sql' { + const value: string; + export default value; +} + +declare module '*.png' { + const value: string; // Base64 data URL + export default value; +} + +declare module '*.txt' { + const value: string; + export default value; +} +``` + +**Best Practices:** + +- **Keep files small** - Imported files are bundled with your code, increasing bundle size and deployment time +- **Use for static data** - Best for configuration, templates, and static assets that don't change frequently +- **Consider external storage** - For large datasets or frequently changing data, use Object Storage or Vector Storage APIs instead +- **Version control** - Commit imported files to your repository to keep them in sync with code + +**Notes:** + +- All imports are processed at build time, not runtime +- Imported files become part of your agent bundle +- File paths are relative to the importing file +- The bundler automatically handles file types by extension + +--- + ## Migrating from v0 For users upgrading from v0, the key architectural changes include: @@ -2689,5 +3008,3 @@ For users upgrading from v0, the key architectural changes include: For complete migration instructions, see the [Migration Guide](/migration-guide). --- - -**This completes the API Reference documentation.** \ No newline at end of file diff --git a/content/v1/migration-guide.mdx b/content/v1/migration-guide.mdx index 8169f0c5..9b87cf6b 100644 --- a/content/v1/migration-guide.mdx +++ b/content/v1/migration-guide.mdx @@ -124,7 +124,7 @@ export default handler; **v1:** ```typescript -import { createAgent, createApp } from '@agentuity/runtime'; +import { createAgent, createRouter } from '@agentuity/runtime'; const agent = createAgent({ metadata: { @@ -141,10 +141,10 @@ const agent = createAgent({ } }); -const app = createApp(); -app.router.post('/my-agent', agent.handler); +const router = createRouter(); +router.post('/my-agent', agent.handler); -export default app.server; +export default router; ``` **Key Changes:** @@ -152,8 +152,8 @@ export default app.server; 2. Use `createAgent()` instead of a plain function 3. Handler receives `(ctx, input)` instead of `(request, response, context)` 4. Return values directly instead of using `response.json()` -5. Create an `app` and register your agent with the router -6. Export `app.server` instead of the handler +5. Use `createRouter()` to define HTTP endpoints and register your agent +6. Export the router instead of the handler **File Structure Change:** In v0, agents were typically single files. In v1, each agent has two files: @@ -169,7 +169,8 @@ The route file imports the agent and invokes it via `ctx.agent.name.run(input)`. v1 introduces optional schema validation for type safety: ```typescript -import { createAgent, createApp } from '@agentuity/runtime'; +// src/agents/typed-agent/agent.ts +import { createAgent } from '@agentuity/runtime'; import { z } from 'zod'; const agent = createAgent({ @@ -198,10 +199,18 @@ const agent = createAgent({ } }); -const app = createApp(); -app.router.post('/typed-agent', agent.handler); +export default agent; +``` -export default app.server; +```typescript +// src/agents/typed-agent/route.ts +import { createRouter } from '@agentuity/runtime'; +import agent from './agent'; + +const router = createRouter(); +router.post('/typed-agent', agent.handler); + +export default router; ``` **Benefits:** @@ -297,9 +306,11 @@ const handler: AgentHandler = async (request, response, context) => { **v1 (HTTP Routes):** ```typescript -const app = createApp(); +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); -app.router.post('/my-agent', async (ctx) => { +router.post('/my-agent', async (ctx) => { // Get JSON body directly from Hono context const data = await ctx.req.json(); @@ -314,6 +325,8 @@ app.router.post('/my-agent', async (ctx) => { return { processed: data }; }); + +export default router; ``` **v1 (Agent with Schema):** @@ -497,9 +510,11 @@ const handler: AgentHandler = async (request, response, context) => { **v1 (Native email route):** ```typescript -const app = createApp(); +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); -app.router.email('support@example.com', async (email, ctx) => { +router.email('support@example.com', async (email, ctx) => { // Email is automatically parsed ctx.logger.info('Email received:', { from: email.fromEmail(), @@ -515,12 +530,14 @@ app.router.email('support@example.com', async (email, ctx) => { return { status: 'processed' }; }); + +export default router; ``` #### WebSocket Routes (NEW) ```typescript -app.router.websocket('/chat', (ctx) => (ws) => { +router.websocket('/chat', (ctx) => (ws) => { ws.onOpen((event) => { ctx.logger.info('Client connected'); ws.send('Welcome!'); @@ -544,7 +561,7 @@ app.router.websocket('/chat', (ctx) => (ws) => { #### Server-Sent Events (NEW) ```typescript -app.router.sse('/updates', (ctx) => async (stream) => { +router.sse('/updates', (ctx) => async (stream) => { // Send updates to client await stream.write({ event: 'started', data: 'Processing...' }); @@ -561,7 +578,7 @@ app.router.sse('/updates', (ctx) => async (stream) => { #### Cron Routes (NEW) ```typescript -app.router.cron('0 0 * * *', async (ctx) => { +router.cron('0 0 * * *', async (ctx) => { // Runs daily at midnight ctx.logger.info('Running daily job'); @@ -803,7 +820,7 @@ export default handler; **v1:** ```typescript -import { createAgent, createApp } from '@agentuity/runtime'; +import { createAgent, createRouter } from '@agentuity/runtime'; import { z } from 'zod'; const agent = createAgent({ @@ -816,10 +833,10 @@ const agent = createAgent({ } }); -const app = createApp(); -app.router.post('/greet', agent.handler); +const router = createRouter(); +router.post('/greet', agent.handler); -export default app.server; +export default router; ``` --- @@ -848,7 +865,7 @@ export default handler; **v1:** ```typescript -import { createAgent, createApp } from '@agentuity/runtime'; +import { createAgent, createRouter } from '@agentuity/runtime'; import { z } from 'zod'; const agent = createAgent({ @@ -870,10 +887,10 @@ const agent = createAgent({ } }); -const app = createApp(); -app.router.post('/store', agent.handler); +const router = createRouter(); +router.post('/store', agent.handler); -export default app.server; +export default router; ``` --- @@ -911,7 +928,7 @@ export default handler; **v1:** ```typescript -import { createAgent, createApp } from '@agentuity/runtime'; +import { createAgent, createRouter } from '@agentuity/runtime'; const agent = createAgent({ handler: async (ctx, input) => { @@ -925,10 +942,10 @@ const agent = createAgent({ } }); -const app = createApp(); -app.router.post('/workflow', agent.handler); +const router = createRouter(); +router.post('/workflow', agent.handler); -export default app.server; +export default router; ``` --- @@ -963,14 +980,14 @@ import { ... } from '@agentuity/runtime'; #### Issue 3: "Handler is not a function" -**Cause**: You're exporting the agent directly instead of the app server. +**Cause**: You're exporting the agent directly instead of the router. -**Solution**: Make sure you export the app server: +**Solution**: Make sure you export the router: ```typescript -const app = createApp(); -app.router.post('/agent', agent.handler); +const router = createRouter(); +router.post('/agent', agent.handler); -export default app.server; // Not 'agent' or 'handler' +export default router; // Not 'agent' or 'handler' ``` --- @@ -999,15 +1016,15 @@ const agent = createAgent({ #### Issue 5: "Cannot access other agents" -**Cause**: Agents aren't properly registered with the app router. +**Cause**: Agents aren't properly registered with the router. **Solution**: Ensure all agents are registered: ```typescript -const app = createApp(); +const router = createRouter(); // Register each agent -app.router.post('/agent1', agent1.handler); -app.router.post('/agent2', agent2.handler); +router.post('/agent1', agent1.handler); +router.post('/agent2', agent2.handler); // Now they can access each other via ctx.agent ``` From 625d9b7e7b9123e61d078e38045e0c9013392525 Mon Sep 17 00:00:00 2001 From: parteeksingh24 Date: Fri, 21 Nov 2025 13:32:13 -0800 Subject: [PATCH 10/63] Clarify context naming in routes vs agents --- content/v1/Examples/index.mdx | 90 ++++----- content/v1/Guides/agent-communication.mdx | 176 ++++++++--------- content/v1/Guides/agent-logging.mdx | 72 +++---- content/v1/Guides/context-types.mdx | 181 ++++++++++++++++++ content/v1/Guides/evaluations.mdx | 62 +++--- content/v1/Guides/events.mdx | 32 ++-- content/v1/Guides/key-value-storage.mdx | 50 ++--- content/v1/Guides/object-storage.mdx | 68 +++---- content/v1/Guides/routing-triggers.mdx | 41 ++-- content/v1/Guides/schema-validation.mdx | 28 +-- content/v1/Guides/sessions-threads.mdx | 106 +++++----- content/v1/Guides/subagents.mdx | 60 +++--- content/v1/Introduction/architecture.mdx | 22 +-- content/v1/Introduction/core-concepts.mdx | 28 +-- content/v1/SDK/api-reference.mdx | 23 ++- content/v1/SDK/core-concepts.mdx | 111 +++++------ content/v1/SDK/error-handling.mdx | 70 +++---- .../developers/01-intro-to-agents.mdx | 46 ++--- content/v1/migration-guide.mdx | 10 +- 19 files changed, 743 insertions(+), 533 deletions(-) create mode 100644 content/v1/Guides/context-types.mdx diff --git a/content/v1/Examples/index.mdx b/content/v1/Examples/index.mdx index c825b901..3a1bd134 100644 --- a/content/v1/Examples/index.mdx +++ b/content/v1/Examples/index.mdx @@ -15,8 +15,8 @@ Here's a basic agent that processes requests and returns responses: import { createAgent } from '@agentuity/runtime'; const agent = createAgent({ - handler: async (c, input) => { - c.logger.info('Received request'); + handler: async (ctx, input) => { + ctx.logger.info('Received request'); return { message: 'Hello, World!', @@ -31,7 +31,7 @@ export default agent; **Key Points:** - Direct return values (no response object needed) - Input is automatically available -- Logger accessed via `c.logger` +- Logger accessed via `ctx.logger` - Returns plain JavaScript objects ### Type-Safe Agent with Validation @@ -53,7 +53,7 @@ const agent = createAgent({ status: z.enum(['active', 'pending']) }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { // input.email and input.age are fully typed and validated return { userId: crypto.randomUUID(), @@ -83,25 +83,25 @@ This example demonstrates structured logging for monitoring and debugging: import { createAgent } from '@agentuity/runtime'; const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { // Different log levels - c.logger.info('Processing request', { - sessionId: c.sessionId, + ctx.logger.info('Processing request', { + sessionId: ctx.sessionId, inputSize: JSON.stringify(input).length }); - c.logger.debug('Detailed processing info', { data: input }); + ctx.logger.debug('Detailed processing info', { data: input }); try { const result = await processData(input); - c.logger.info('Processing successful', { + ctx.logger.info('Processing successful', { resultSize: JSON.stringify(result).length }); return result; } catch (error) { - c.logger.error('Processing failed', { + ctx.logger.error('Processing failed', { error: error.message, stack: error.stack }); @@ -155,11 +155,11 @@ const agent = createAgent({ success: v.boolean() }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { const { action, userId, preferences } = input; if (action === 'get') { - const result = await c.kv.get('user-preferences', userId); + const result = await ctx.kv.get('user-preferences', userId); if (!result.exists) { return { success: false, message: 'No preferences found' }; @@ -170,7 +170,7 @@ const agent = createAgent({ } if (action === 'set') { - await c.kv.set( + await ctx.kv.set( 'user-preferences', userId, preferences, @@ -181,7 +181,7 @@ const agent = createAgent({ } // Delete - await c.kv.delete('user-preferences', userId); + await ctx.kv.delete('user-preferences', userId); return { success: true, message: 'Preferences deleted' }; } }); @@ -190,8 +190,8 @@ export default agent; ``` **Key Points:** -- `c.kv.get()` returns a result with `exists` flag -- `c.kv.set()` supports optional TTL for expiring data +- `ctx.kv.get()` returns a result with `exists` flag +- `ctx.kv.set()` supports optional TTL for expiring data - Data can be stored as JSON objects or strings - Storage is namespaced by the first parameter @@ -226,7 +226,7 @@ const agent = createAgent({ deletedCount: 'number?' }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { const { action, query, products } = input; if (action === 'index') { @@ -329,7 +329,7 @@ const agent = createAgent({ message: v.optional(v.string()) }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { const { action, filename, data, expiresIn } = input; if (action === 'upload') { @@ -404,8 +404,8 @@ Calling other agents to build workflows: import { createAgent } from '@agentuity/runtime'; const agent = createAgent({ - handler: async (c, input) => { - c.logger.info('Starting multi-agent workflow'); + handler: async (ctx, input) => { + ctx.logger.info('Starting multi-agent workflow'); // Call enrichment agent const enriched = await c.agent.enrichmentAgent.run({ @@ -462,7 +462,7 @@ const agent = createAgent({ confidence: z.number() }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { // Search vector database for relevant context const results = await c.vector.search('knowledge-base', { query: input.question, @@ -550,9 +550,9 @@ const agent = createAgent({ error: z.string().optional() }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { try { - c.logger.info(`Processing resource: ${input.resourceId}`); + ctx.logger.info(`Processing resource: ${input.resourceId}`); // Simulate resource lookup const resource = await lookupResource(input.resourceId, ctx); @@ -567,7 +567,7 @@ const agent = createAgent({ } catch (error) { // Handle different error types if (error instanceof ValidationError) { - c.logger.warn(`Validation error: ${error.message}`); + ctx.logger.warn(`Validation error: ${error.message}`); return { error: 'Validation error', message: error.message @@ -575,7 +575,7 @@ const agent = createAgent({ } if (error instanceof ResourceNotFoundError) { - c.logger.warn(`Resource not found: ${error.message}`); + ctx.logger.warn(`Resource not found: ${error.message}`); return { error: 'Resource not found', message: error.message @@ -583,7 +583,7 @@ const agent = createAgent({ } // Handle unexpected errors - c.logger.error('Unexpected error', error); + ctx.logger.error('Unexpected error', error); return { error: 'Internal server error', message: 'An unexpected error occurred' @@ -647,7 +647,7 @@ const agent = createAgent({ }) }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { // Request-scoped state (cleared after request) c.state.set('startTime', Date.now()); @@ -701,8 +701,8 @@ const agent = createAgent({ input: z.object({ task: z.string() }), output: z.object({ result: z.string() }) }, - handler: async (c, input) => { - c.logger.info('Processing task', { task: input.task }); + handler: async (ctx, input) => { + ctx.logger.info('Processing task', { task: input.task }); // Simulate processing await new Promise(resolve => setTimeout(resolve, 100)); @@ -790,8 +790,8 @@ const agent = createAgent({ summary: z.string() }) }, - handler: async (c, input) => { - c.logger.info(`Starting workflow: ${input.query}`); + handler: async (ctx, input) => { + ctx.logger.info(`Starting workflow: ${input.query}`); // Step 1: Search for relevant information const searchResults = await c.agent.searchAgent.run({ @@ -825,7 +825,7 @@ const agent = createAgent({ recommendations ? `, generated ${recommendations.length} recommendations` : '' }`; - c.logger.info('Workflow completed successfully'); + ctx.logger.info('Workflow completed successfully'); return { searchResults, @@ -874,7 +874,7 @@ const agent = createAgent({ estimatedDuration: z.string().optional() }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { const { action, prompt, userId, includeAnalytics = true } = input; if (action === 'stream-with-background') { @@ -1010,9 +1010,9 @@ const router = createRouter(); router.email('support@example.com', async (email, c) => { // Process email with agent const result = await c.agent.emailProcessor.run({ - sender: email.from, - subject: email.subject, - content: email.text || email.html || '' + sender: email.fromEmail() || 'unknown', + subject: email.subject() || 'no subject', + content: email.text() || email.html() || '' }); return c.json({ @@ -1058,7 +1058,7 @@ router.websocket('/chat', (c) => (ws) => { }); ws.onClose(() => { - c.logger.info('Client disconnected'); + ctx.logger.info('Client disconnected'); }); }); @@ -1108,7 +1108,7 @@ router.cron('*/5 * * * *', async (c) => { const health = await c.agent.healthCheck.run({}); if (health.status !== 'ok') { - c.logger.warn('Health check failed', { status: health.status }); + ctx.logger.warn('Health check failed', { status: health.status }); } return c.json({ healthy: health.status === 'ok' }); @@ -1160,7 +1160,7 @@ router.sse('/updates', (c) => async (stream) => { await stream.write({ type: 'complete' }); stream.onAbort(() => { - c.logger.info('Client disconnected'); + ctx.logger.info('Client disconnected'); }); }); @@ -1195,7 +1195,7 @@ const agent = createAgent({ confidence: z.number() }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { // Process query and generate result const result = await processQuery(input.query); @@ -1212,7 +1212,7 @@ agent.createEval({ name: 'confidence-check', description: 'Ensures confidence score meets minimum threshold' }, - handler: async (c, input, output) => { + handler: async (ctx, input, output) => { const passed = output.confidence >= 0.8; return { @@ -1254,7 +1254,7 @@ import { createAgent } from '@agentuity/runtime'; import { SpanStatusCode } from '@opentelemetry/api'; const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { return c.tracer.startActiveSpan('process-request', async (span) => { try { // Add attributes to the span @@ -1303,7 +1303,7 @@ const agent = createAgent({ message: (error as Error).message }); - c.logger.error('Error processing request', error); + ctx.logger.error('Error processing request', error); throw error; } finally { span.end(); @@ -1351,7 +1351,7 @@ const agent = createAgent({ }), stream: true }, - handler: async (c, input) => { + handler: async (ctx, input) => { const { textStream } = streamText({ model: openai('gpt-5-nano'), prompt: input.prompt @@ -1389,7 +1389,7 @@ const agent = createAgent({ }), stream: true }, - handler: async (c, input) => { + handler: async (ctx, input) => { // Call the expert agent and stream its response const response = await c.agent.historyExpert.run({ question: input.question diff --git a/content/v1/Guides/agent-communication.mdx b/content/v1/Guides/agent-communication.mdx index 03e503c6..b372d8d7 100644 --- a/content/v1/Guides/agent-communication.mdx +++ b/content/v1/Guides/agent-communication.mdx @@ -36,9 +36,9 @@ const coordinatorAgent = createAgent({ input: z.object({ text: z.string() }), output: z.object({ result: z.string() }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { // Call another agent - const enriched = await c.agent.enrichmentAgent.run({ + const enriched = await ctx.agent.enrichmentAgent.run({ text: input.text }); @@ -62,19 +62,19 @@ Sequential execution processes data through a series of agents, where each agent ```typescript const pipelineAgent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { // Step 1: Validate input - const validated = await c.agent.validatorAgent.run({ + const validated = await ctx.agent.validatorAgent.run({ data: input.rawData }); // Step 2: Enrich with additional data - const enriched = await c.agent.enrichmentAgent.run({ + const enriched = await ctx.agent.enrichmentAgent.run({ data: validated.cleanData }); // Step 3: Analyze the enriched data - const analyzed = await c.agent.analysisAgent.run({ + const analyzed = await ctx.agent.analysisAgent.run({ data: enriched.enrichedData }); @@ -97,12 +97,12 @@ Parallel execution runs multiple agents simultaneously when their operations are ```typescript const searchAgent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { // Execute all searches in parallel const [webResults, dbResults, vectorResults] = await Promise.all([ - c.agent.webSearchAgent.run({ query: input.query }), - c.agent.databaseAgent.run({ query: input.query }), - c.agent.vectorSearchAgent.run({ query: input.query }) + ctx.agent.webSearchAgent.run({ query: input.query }), + ctx.agent.databaseAgent.run({ query: input.query }), + ctx.agent.vectorSearchAgent.run({ query: input.query }) ]); // Merge and rank results @@ -141,7 +141,7 @@ const routerAgent = createAgent({ schema: { input: z.object({ userMessage: z.string() }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { // Classify intent with Groq (fast inference via AI Gateway) const intent = await generateObject({ model: groq('llama-3.3-70b'), // Groq (fast inference via AI Gateway) for quick classification @@ -151,7 +151,7 @@ const routerAgent = createAgent({ temperature: 0.0 // Deterministic output for routing decisions }); - c.logger.info('Intent classified', { + ctx.logger.info('Intent classified', { type: intent.object.agentType, confidence: intent.object.confidence }); @@ -159,25 +159,25 @@ const routerAgent = createAgent({ // Route based on classified intent switch (intent.object.agentType) { case 'support': - return await c.agent.supportAgent.run({ + return await ctx.agent.supportAgent.run({ message: input.userMessage, context: intent.object.reasoning }); case 'sales': - return await c.agent.salesAgent.run({ + return await ctx.agent.salesAgent.run({ message: input.userMessage, context: intent.object.reasoning }); case 'technical': - return await c.agent.technicalAgent.run({ + return await ctx.agent.technicalAgent.run({ message: input.userMessage, context: intent.object.reasoning }); case 'billing': - return await c.agent.billingAgent.run({ + return await ctx.agent.billingAgent.run({ message: input.userMessage, context: intent.object.reasoning }); @@ -200,9 +200,9 @@ Build complex pipelines with checkpoints and conditional branching: ```typescript const contentModerationAgent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { // Stage 1: Initial screening - const screening = await c.agent.screeningAgent.run({ + const screening = await ctx.agent.screeningAgent.run({ content: input.text }); @@ -216,13 +216,13 @@ const contentModerationAgent = createAgent({ // Stage 2: Detailed analysis (parallel) const [sentiment, toxicity, pii] = await Promise.all([ - c.agent.sentimentAgent.run({ text: input.text }), - c.agent.toxicityAgent.run({ text: input.text }), - c.agent.piiDetectionAgent.run({ text: input.text }) + ctx.agent.sentimentAgent.run({ text: input.text }), + ctx.agent.toxicityAgent.run({ text: input.text }), + ctx.agent.piiDetectionAgent.run({ text: input.text }) ]); // Stage 3: Final decision - const decision = await c.agent.decisionAgent.run({ + const decision = await ctx.agent.decisionAgent.run({ screening, sentiment, toxicity, @@ -248,16 +248,16 @@ const documentAnalysisAgent = createAgent({ sections: z.array(z.string()) }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { // Fan-out: Analyze each section in parallel const sectionAnalyses = await Promise.all( input.sections.map(section => - c.agent.sectionAnalyzer.run({ text: section }) + ctx.agent.sectionAnalyzer.run({ text: section }) ) ); // Fan-in: Aggregate results - const summary = await c.agent.summaryAgent.run({ + const summary = await ctx.agent.summaryAgent.run({ analyses: sectionAnalyses }); @@ -274,7 +274,7 @@ const documentAnalysisAgent = createAgent({ ```typescript const results = await Promise.allSettled( input.items.map(item => - c.agent.processingAgent.run({ item }) + ctx.agent.processingAgent.run({ item }) ) ); @@ -287,7 +287,7 @@ const failed = results .map(r => r.reason); if (failed.length > 0) { - c.logger.warn('Some operations failed', { failed }); + ctx.logger.warn('Some operations failed', { failed }); } return { successful, failed: failed.length }; @@ -301,12 +301,12 @@ By default, errors propagate through the call chain, stopping execution immediat ```typescript const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { // If validatorAgent throws, execution stops here - const validated = await c.agent.validatorAgent.run(input); + const validated = await ctx.agent.validatorAgent.run(input); // This line never executes if validation fails - const processed = await c.agent.processorAgent.run(validated); + const processed = await ctx.agent.processorAgent.run(validated); return processed; } @@ -321,23 +321,23 @@ For optional operations, catch errors and continue with reduced functionality: ```typescript const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { let enrichedData = input.data; // Try to enrich, but continue if it fails try { - const enrichment = await c.agent.enrichmentAgent.run({ + const enrichment = await ctx.agent.enrichmentAgent.run({ data: input.data }); enrichedData = enrichment.data; } catch (error) { - c.logger.warn('Enrichment failed, using original data', { + ctx.logger.warn('Enrichment failed, using original data', { error: error instanceof Error ? error.message : String(error) }); } // Process with enriched data (or original if enrichment failed) - return await c.agent.processorAgent.run({ + return await ctx.agent.processorAgent.run({ data: enrichedData }); } @@ -377,10 +377,10 @@ async function callWithRetry( } const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { // Retry unreliable external agent call const result = await callWithRetry(() => - c.agent.externalServiceAgent.run(input) + ctx.agent.externalServiceAgent.run(input) ); return result; @@ -394,15 +394,15 @@ Provide alternative paths when primary agents fail: ```typescript const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { try { // Try primary agent - return await c.agent.primaryAgent.run(input); + return await ctx.agent.primaryAgent.run(input); } catch (error) { - c.logger.warn('Primary agent failed, using fallback', { error }); + ctx.logger.warn('Primary agent failed, using fallback', { error }); // Fall back to alternative agent - return await c.agent.fallbackAgent.run(input); + return await ctx.agent.fallbackAgent.run(input); } } }); @@ -416,14 +416,14 @@ Transform agent outputs to match the expected input of downstream agents: ```typescript const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { // Agent 1 returns { analysisResult: {...} } - const analysis = await c.agent.analysisAgent.run({ + const analysis = await ctx.agent.analysisAgent.run({ text: input.text }); // Agent 2 expects { data: {...}, metadata: {...} } - const processed = await c.agent.processingAgent.run({ + const processed = await ctx.agent.processingAgent.run({ data: analysis.analysisResult, metadata: { timestamp: new Date().toISOString(), @@ -468,24 +468,24 @@ Build pipelines that progressively transform data: ```typescript const transformationAgent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { // Raw text → Structured data - const structured = await c.agent.parserAgent.run({ + const structured = await ctx.agent.parserAgent.run({ text: input.rawText }); // Structured data → Validated data - const validated = await c.agent.validatorAgent.run({ + const validated = await ctx.agent.validatorAgent.run({ data: structured.parsed }); // Validated data → Enriched data - const enriched = await c.agent.enrichmentAgent.run({ + const enriched = await ctx.agent.enrichmentAgent.run({ data: validated.clean }); // Enriched data → Final output - const final = await c.agent.formatterAgent.run({ + const final = await ctx.agent.formatterAgent.run({ data: enriched.enriched }); @@ -508,9 +508,9 @@ const researchAgent = createAgent({ sources: z.array(z.string()) }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { // Step 1: Search for relevant information - const searchResults = await c.agent.searchAgent.run({ + const searchResults = await ctx.agent.searchAgent.run({ query: input.query, limit: 10 }); @@ -518,12 +518,12 @@ const researchAgent = createAgent({ // Step 2: Summarize findings (parallel) const summaries = await Promise.all( searchResults.results.map(result => - c.agent.summaryAgent.run({ text: result.content }) + ctx.agent.summaryAgent.run({ text: result.content }) ) ); // Step 3: Analyze and extract insights - const analysis = await c.agent.analysisAgent.run({ + const analysis = await ctx.agent.analysisAgent.run({ summaries: summaries.map(s => s.summary), originalQuery: input.query }); @@ -547,9 +547,9 @@ const approvalAgent = createAgent({ requester: z.string() }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { // Step 1: Validate request format - const validation = await c.agent.validatorAgent.run({ + const validation = await ctx.agent.validatorAgent.run({ request: input.request }); @@ -562,7 +562,7 @@ const approvalAgent = createAgent({ } // Step 2: Check user permissions - const permissions = await c.agent.permissionsAgent.run({ + const permissions = await ctx.agent.permissionsAgent.run({ userId: input.requester, action: input.request.action }); @@ -576,13 +576,13 @@ const approvalAgent = createAgent({ // Step 3: Risk assessment (parallel checks) const [financialRisk, securityRisk, complianceCheck] = await Promise.all([ - c.agent.financialRiskAgent.run(input.request), - c.agent.securityRiskAgent.run(input.request), - c.agent.complianceAgent.run(input.request) + ctx.agent.financialRiskAgent.run(input.request), + ctx.agent.securityRiskAgent.run(input.request), + ctx.agent.complianceAgent.run(input.request) ]); // Step 4: Final approval decision - const decision = await c.agent.decisionAgent.run({ + const decision = await ctx.agent.decisionAgent.run({ request: input.request, permissions, financialRisk, @@ -599,7 +599,7 @@ const approvalAgent = createAgent({ ```typescript const dataEnrichmentAgent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { // Start with raw user data let userData = input.userData; @@ -609,9 +609,9 @@ const dataEnrichmentAgent = createAgent({ behavioralData, preferenceData ] = await Promise.all([ - c.agent.demographicAgent.run({ userId: userData.id }), - c.agent.behavioralAgent.run({ userId: userData.id }), - c.agent.preferenceAgent.run({ userId: userData.id }) + ctx.agent.demographicAgent.run({ userId: userData.id }), + ctx.agent.behavioralAgent.run({ userId: userData.id }), + ctx.agent.preferenceAgent.run({ userId: userData.id }) ]); // Merge all data @@ -623,19 +623,19 @@ const dataEnrichmentAgent = createAgent({ }; // Generate insights from enriched data - const insights = await c.agent.insightsAgent.run({ + const insights = await ctx.agent.insightsAgent.run({ enrichedData: merged }); // Store for future use try { - await c.kv.set('enriched-users', userData.id, { + await ctx.kv.set('enriched-users', userData.id, { data: merged, insights: insights, updatedAt: new Date().toISOString() }, { ttl: 86400 }); // 24 hours } catch (error) { - c.logger.warn('Failed to cache enriched data', { error }); + ctx.logger.warn('Failed to cache enriched data', { error }); } return { @@ -654,7 +654,7 @@ For parent-child agent hierarchies, the SDK provides specialized patterns. Subag ```typescript // Call a subagent from anywhere -const result = await c.agent.team.members.run({ +const result = await ctx.agent.team.members.run({ action: 'list' }); @@ -682,7 +682,7 @@ const analysisAgent = createAgent({ /* analyzes data */ }); // Bad - monolithic agent doing everything const megaAgent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { // Validates, enriches, analyzes all in one place } }); @@ -702,7 +702,7 @@ const sourceAgent = createAgent({ metadata: z.object({ timestamp: z.string() }) }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { return { data: 'result', metadata: { timestamp: new Date().toISOString() } @@ -717,9 +717,9 @@ const consumerAgent = createAgent({ metadata: z.object({ timestamp: z.string() }) }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { // TypeScript knows the exact shape of input - const result = await c.agent.sourceAgent.run({}); + const result = await ctx.agent.sourceAgent.run({}); // This is type-safe - TypeScript validates compatibility return await processData(result); @@ -734,15 +734,15 @@ Choose the right error handling strategy for each operation: **Fail-fast** for critical operations: ```typescript // No try-catch - let errors propagate -const validated = await c.agent.validatorAgent.run(input); +const validated = await ctx.agent.validatorAgent.run(input); ``` **Graceful degradation** for optional operations: ```typescript try { - await c.agent.optionalAgent.run(input); + await ctx.agent.optionalAgent.run(input); } catch (error) { - c.logger.warn('Optional operation failed', { error }); + ctx.logger.warn('Optional operation failed', { error }); } ``` @@ -752,18 +752,18 @@ Use logging and tracing to monitor multi-agent workflows: ```typescript const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { const startTime = Date.now(); - c.logger.info('Starting multi-agent workflow', { - sessionId: c.sessionId + ctx.logger.info('Starting multi-agent workflow', { + sessionId: ctx.sessionId }); - const result = await c.agent.processingAgent.run(input); + const result = await ctx.agent.processingAgent.run(input); - c.logger.info('Workflow completed', { + ctx.logger.info('Workflow completed', { duration: Date.now() - startTime, - sessionId: c.sessionId + sessionId: ctx.sessionId }); return result; @@ -777,16 +777,16 @@ Agent calls execute within the same session context, sharing state: ```typescript const coordinatorAgent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { // Store data in thread state ctx.thread.state.set('startTime', Date.now()); ctx.thread.state.set('userId', input.userId); // Called agents can access the same thread state - const result = await c.agent.processingAgent.run(input); + const result = await ctx.agent.processingAgent.run(input); // All agents share the same sessionId - c.logger.info('Session ID:', c.sessionId); + ctx.logger.info('Session ID:', ctx.sessionId); return result; } @@ -805,7 +805,7 @@ When using LLM-based routing, handle classification failures gracefully: ```typescript const routerAgent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { try { const intent = await generateObject({ model: groq('llama-3.3-70b'), @@ -817,25 +817,25 @@ const routerAgent = createAgent({ // Route based on intent switch (intent.object.agentType) { case 'support': - return await c.agent.supportAgent.run({ + return await ctx.agent.supportAgent.run({ message: input.userMessage, context: intent.object.reasoning }); case 'sales': - return await c.agent.salesAgent.run({ + return await ctx.agent.salesAgent.run({ message: input.userMessage, context: intent.object.reasoning }); case 'technical': - return await c.agent.technicalAgent.run({ + return await ctx.agent.technicalAgent.run({ message: input.userMessage, context: intent.object.reasoning }); } } catch (error) { - c.logger.error('Intent classification failed', { + ctx.logger.error('Intent classification failed', { error: error instanceof Error ? error.message : String(error) }); diff --git a/content/v1/Guides/agent-logging.mdx b/content/v1/Guides/agent-logging.mdx index 20773f46..6eaefe34 100644 --- a/content/v1/Guides/agent-logging.mdx +++ b/content/v1/Guides/agent-logging.mdx @@ -51,26 +51,26 @@ TODO: Update screenshot for v1 Detailed Log Entry View import { createAgent } from '@agentuity/runtime'; const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { // TRACE: Very detailed step-by-step execution (hidden in production) - c.logger.trace('RAG retrieval step 1: Embedding query'); - c.logger.trace('RAG retrieval step 2: Searching vector store', { query: input.question }); - c.logger.trace('RAG retrieval step 3: Ranking results', { count: results.length }); + ctx.logger.trace('RAG retrieval step 1: Embedding query'); + ctx.logger.trace('RAG retrieval step 2: Searching vector store', { query: input.question }); + ctx.logger.trace('RAG retrieval step 3: Ranking results', { count: results.length }); // DEBUG: Detailed information for debugging - c.logger.debug('Cache lookup', { key: cacheKey, hit: true }); + ctx.logger.debug('Cache lookup', { key: cacheKey, hit: true }); // INFO: Normal flow and state changes - c.logger.info('Processing user request', { userId: input.userId, action: 'search' }); + ctx.logger.info('Processing user request', { userId: input.userId, action: 'search' }); // WARN: Potential issues that don't stop execution - c.logger.warn('Rate limit approaching', { remaining: 5, limit: 100 }); + ctx.logger.warn('Rate limit approaching', { remaining: 5, limit: 100 }); // ERROR: Failures requiring attention but execution continues - c.logger.error('Failed to fetch data', { error: error.message, retrying: true }); + ctx.logger.error('Failed to fetch data', { error: error.message, retrying: true }); // FATAL: Critical errors that terminate execution - // c.logger.fatal('Application cannot continue: Critical initialization failure'); + // ctx.logger.fatal('Application cannot continue: Critical initialization failure'); return { result: 'success' }; } @@ -121,9 +121,9 @@ Use structured data to provide context and make logs easier to parse and analyze import { createAgent } from '@agentuity/runtime'; const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { // Good: Use objects for structured context - c.logger.info('API call completed', { + ctx.logger.info('API call completed', { endpoint: '/api/users', method: 'GET', duration: 234, @@ -135,7 +135,7 @@ const agent = createAgent({ const orderId = input.order.id; const itemCount = input.order.items.length; - c.logger.info('Order processed', { + ctx.logger.info('Order processed', { orderId, itemCount, total: input.order.total @@ -154,9 +154,9 @@ Log enough information to understand what happened without re-running: import { createAgent } from '@agentuity/runtime'; const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { // Good - includes context - c.logger.info('Order processed', { + ctx.logger.info('Order processed', { orderId: input.order.id, customerId: input.customer.id, total: input.order.total, @@ -164,7 +164,7 @@ const agent = createAgent({ }); // Less helpful - missing context - // c.logger.info('Order done'); + // ctx.logger.info('Order done'); return { success: true }; } @@ -179,12 +179,12 @@ Add Agentuity-specific information to help with debugging and monitoring: import { createAgent } from '@agentuity/runtime'; const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { // Include available Agentuity context - c.logger.info('Agent processing request', { + ctx.logger.info('Agent processing request', { agentName: c.agentName, sessionId: c.sessionId - // Note: c.agent.id and req.trigger not available in v1 + // Note: ctx.agent.id and req.trigger not available in v1 }); // For errors, include additional context @@ -192,7 +192,7 @@ const agent = createAgent({ const result = await processData(input); return result; } catch (error) { - c.logger.error('Agent execution failed', { + ctx.logger.error('Agent execution failed', { agentName: c.agentName, sessionId: c.sessionId, error: error.message @@ -213,19 +213,19 @@ Help future debugging by logging key decisions: import { createAgent } from '@agentuity/runtime'; const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { const cacheKey = `user:${input.userId}:profile`; const cached = await c.kv.get('cache', cacheKey); if (cached.exists) { const cacheAge = Date.now() - cached.data.timestamp; - c.logger.info('Using cached response', { + ctx.logger.info('Using cached response', { cacheKey, age: cacheAge }); return cached.data.value; } else { - c.logger.info('Cache miss, fetching fresh data', { + ctx.logger.info('Cache miss, fetching fresh data', { cacheKey, reason: 'not found' }); @@ -245,7 +245,7 @@ Create child loggers to organize logging for different parts of your workflow: import { createAgent } from '@agentuity/runtime'; const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { // Create child loggers for different tasks const orderLogger = c.logger.child({ component: 'order-processing' }); const paymentLogger = c.logger.child({ component: 'payment' }); @@ -298,27 +298,27 @@ const ragAgent = createAgent({ input: z.object({ question: z.string() }), output: z.object({ answer: z.string(), sources: z.array(z.string()) }) }, - handler: async (c, input) => { - c.logger.trace('RAG pipeline started'); + handler: async (ctx, input) => { + ctx.logger.trace('RAG pipeline started'); // Step 1: Embedding - c.logger.trace('Generating query embedding'); + ctx.logger.trace('Generating query embedding'); const embedding = await generateEmbedding(input.question); - c.logger.debug('Query embedding generated', { dimensions: embedding.length }); + ctx.logger.debug('Query embedding generated', { dimensions: embedding.length }); // Step 2: Retrieval - c.logger.trace('Searching vector store'); + ctx.logger.trace('Searching vector store'); const results = await c.vector.search('knowledge-base', { query: input.question, limit: 5, similarity: 0.7 }); - c.logger.info('Retrieved documents', { count: results.length }); + ctx.logger.info('Retrieved documents', { count: results.length }); // Step 3: Generation - c.logger.trace('Generating answer from context'); + ctx.logger.trace('Generating answer from context'); const answer = await generateAnswer(input.question, results); - c.logger.info('Answer generated', { length: answer.length }); + ctx.logger.info('Answer generated', { length: answer.length }); return { answer, @@ -334,7 +334,7 @@ const ragAgent = createAgent({ import { createAgent } from '@agentuity/runtime'; const coordinatorAgent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { const workflowLogger = c.logger.child({ workflow: 'data-processing' }); workflowLogger.info('Workflow started', { inputSize: input.data.length }); @@ -370,11 +370,11 @@ const coordinatorAgent = createAgent({ import { createAgent } from '@agentuity/runtime'; const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { const startTime = Date.now(); try { - c.logger.info('Processing request', { + ctx.logger.info('Processing request', { agentName: c.agentName, sessionId: c.sessionId, userId: input.userId @@ -383,7 +383,7 @@ const agent = createAgent({ const result = await performComplexOperation(input); const duration = Date.now() - startTime; - c.logger.info('Request completed successfully', { + ctx.logger.info('Request completed successfully', { duration, resultSize: JSON.stringify(result).length }); @@ -393,7 +393,7 @@ const agent = createAgent({ } catch (error) { const duration = Date.now() - startTime; - c.logger.error('Request failed', { + ctx.logger.error('Request failed', { agentName: c.agentName, sessionId: c.sessionId, userId: input.userId, diff --git a/content/v1/Guides/context-types.mdx b/content/v1/Guides/context-types.mdx new file mode 100644 index 00000000..08b6b4cb --- /dev/null +++ b/content/v1/Guides/context-types.mdx @@ -0,0 +1,181 @@ +--- +title: Understanding Context Types +description: Learn about Agentuity's two distinct context types - AgentContext for business logic and Router Context for HTTP handling +--- + +Agentuity v1 uses two distinct context types depending on where you're writing code. Understanding when and how to use each context is fundamental to working effectively with the SDK. + +## The Two Context Types + +Agentuity provides two context types, each designed for a specific purpose: + +- **AgentContext**: Used in `agent.ts` files for business logic (no HTTP access) +- **Router Context (Hono)**: Used in `route.ts` files for HTTP handling (has HTTP + agent services) + +**Important:** The distinction is **type-based**, not **name-based**. In SDK examples, we use `ctx` for agent handlers and `c` for router handlers to visually distinguish the two context types. What differs is the **type** and **capabilities**. + +## AgentContext - For Business Logic + +The `AgentContext` is used in agent handlers defined in `agent.ts` files. This context is designed for pure business logic and provides access to Agentuity services without any HTTP-specific functionality. + +**Type:** `AgentContext` + +**Used in:** `agent.ts` files + +**Provides:** +- Agent services (`.agent`, `.kv`, `.vector`, `.objectstore`, etc.) +- State management (`.state`, `.session`, `.thread`) +- Logging and tracing (`.logger`, `.tracer`) +- No HTTP access - input comes through validated parameters + +**Example:** + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const agent = createAgent({ + handler: async (ctx, input) => { + // Business logic only - no HTTP access + ctx.logger.info('Processing'); + await ctx.kv.set('key', 'value'); + return { result: 'done' }; // Direct return + } +}); +``` + +In agent handlers, the input is passed as a separate parameter and is already validated against your schema. The context provides services but no HTTP-specific methods. + +## RouterContext - For HTTP Handling + +The Router Context is Hono's Context object, used in route handlers defined in `route.ts` files. This context extends Hono's functionality with Agentuity services, giving you both HTTP capabilities and access to the agent system. + +**Type:** Hono Context (extends `Context`) + +**Used in:** `route.ts` files + +**Provides:** +- HTTP request access (`.req`) +- HTTP response builders (`.json()`, `.text()`, `.html()`, etc.) +- Agent services (`.agent`, `.kv`, `.vector`, `.objectstore`, etc.) +- Logging and tracing (`.logger`, `.tracer`) + +**Example:** + +```typescript +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +router.post('/api', async (c) => { + // HTTP handling + agent services + const body = await c.req.json(); // HTTP request access + const result = await c.agent.processor.run(body); + return c.json({ result }); // HTTP response +}); +``` + +In route handlers, you access request data through `c.req` and return responses using Hono's builder methods like `c.json()`. + +## Key Differences + +Here's a quick comparison of the two context types: + +| Feature | Router Context (Hono) | Agent Context | +|---------|----------------------|---------------| +| **Type** | Hono Context | `AgentContext` | +| **Used in** | `route.ts` files | `agent.ts` files | +| **Request access** | `c.req` (Hono Request) | Direct `input` parameter (validated) | +| **Response** | Builder methods (`.json()`, `.text()`) | Direct returns | +| **Services** | `.agent`, `.kv`, `.logger`, etc. | `.agent`, `.kv`, `.logger`, etc. | +| **State management** | Via Hono middleware | Built-in (`.state`, `.session`, `.thread`) | + +Both contexts provide access to the same Agentuity services, but differ in how they handle input and output. + +## Common Patterns + +### Calling Agents from Routes + +A common pattern is to receive HTTP requests in routes, then delegate business logic to agents: + +```typescript +// route.ts - HTTP handling +router.post('/process', async (c) => { + const body = await c.req.json(); + + // Call agent to handle business logic + const result = await c.agent.processor.run({ + data: body.data + }); + + return c.json({ result }); +}); +``` + +```typescript +// agent.ts - Business logic +const processor = createAgent({ + handler: async (ctx, input) => { + // Pure business logic + ctx.logger.info('Processing data', { input }); + const processed = await processData(input.data); + return { processed }; + } +}); +``` + +### When to Use Which Context + +**Use AgentContext when:** +- Writing reusable business logic +- Building agents that can be called from multiple sources (routes, schedules, other agents) +- You need state management (`.state`, `.session`, `.thread`) +- You don't need HTTP-specific functionality + +**Use Router Context when:** +- Handling HTTP requests and responses +- Need access to headers, cookies, or raw request data +- Building REST APIs or webhooks +- Need HTTP-specific response types (redirects, streaming, etc.) + +### Naming Conventions + +In SDK examples, we use a naming convention to visually distinguish the two context types: + +- **Agent handlers**: Use `ctx` (e.g., `handler: async (ctx, input) => { ... }`) +- **Router handlers**: Use `c` (e.g., `router.get('/api', async (c) => { ... })`) + +This convention makes it immediately clear which context type you're working with when reading code. However, you can use any variable name you prefer - `c`, `ctx`, `context`, etc. The **type** is what matters, not the name. + +```typescript +// Agent handler - uses ctx in SDK examples +handler: async (ctx, input) => { ... } + +// Route handler - uses c in SDK examples +router.get('/api', async (c) => { ... }) +``` + +When a context parameter is required but unused in your handler, use an underscore prefix: + +```typescript +// Context required but unused +handler: async (_ctx, input) => { + return { result: input.value * 2 }; +} +``` + +## Best Practices + +1. **Be consistent with naming**: The SDK examples use `ctx` for agent handlers and `c` for router handlers. This convention helps visually distinguish the two context types. Stick with this convention for consistency unless your team prefers otherwise. + +2. **Focus on types, not names**: Don't assume a parameter is one context type or another based on its variable name. Check the type definition. + +3. **Separate concerns**: Keep HTTP logic in routes, business logic in agents. This makes your code more testable and reusable. + +4. **Use underscore prefix for unused parameters**: If your route handler doesn't use the context, name it `_c` to indicate this intentionally. + +5. **Type your agent handlers**: Always explicitly type the context parameter in agent handlers as `AgentContext` for clarity: + ```typescript + handler: async (ctx: AgentContext, input) => { ... } + ``` + +6. **Let TypeScript guide you**: If you try to access `ctx.req` in an agent handler or `c.state` in a route handler, TypeScript will catch these errors at compile time. \ No newline at end of file diff --git a/content/v1/Guides/evaluations.mdx b/content/v1/Guides/evaluations.mdx index 379c0f88..429b930a 100644 --- a/content/v1/Guides/evaluations.mdx +++ b/content/v1/Guides/evaluations.mdx @@ -39,7 +39,7 @@ const agent = createAgent({ input: z.object({ question: z.string() }), output: z.object({ answer: z.string(), confidence: z.number() }), }, - handler: async (c, input) => { + handler: async (ctx, input) => { const answer = await generateAnswer(input.question); return { answer, confidence: 0.95 }; }, @@ -51,7 +51,7 @@ agent.createEval({ name: 'confidence-check', description: 'Ensures confidence score meets minimum threshold' }, - handler: async (c, input, output) => { + handler: async (ctx, input, output) => { const passed = output.confidence >= 0.8; return { @@ -102,14 +102,14 @@ const agent = createAgent({ input: z.object({ query: z.string() }), output: z.object({ result: z.string() }), }, - handler: async (c, input) => { + handler: async (ctx, input) => { return { result: `Processed: ${input.query}` }; }, }); agent.createEval({ metadata: { name: 'query-in-result' }, - handler: async (c, input, output) => { + handler: async (ctx, input, output) => { // Handler receives both input and output const queryInResult = output.result.includes(input.query); @@ -131,14 +131,14 @@ const agent = createAgent({ schema: { input: z.object({ message: z.string() }), }, - handler: async (c, input) => { + handler: async (ctx, input) => { return `Received: ${input.message}`; }, }); agent.createEval({ metadata: { name: 'input-validation' }, - handler: async (c, input) => { + handler: async (ctx, input) => { // Handler receives only input const isValid = input.message.length > 0 && input.message.length <= 500; @@ -174,7 +174,7 @@ Use for clear yes/no validation (compliance checks, format verification). ```typescript agent.createEval({ metadata: { name: 'length-check' }, - handler: async (c, input, output) => { + handler: async (ctx, input, output) => { const passed = output.answer.length >= 50; return { success: true, @@ -203,7 +203,7 @@ Use for quality measurement and A/B testing. ```typescript agent.createEval({ metadata: { name: 'quality-score' }, - handler: async (c, input, output) => { + handler: async (ctx, input, output) => { let score = 0; if (output.answer.length >= 100) score += 0.4; if (output.answer.includes(input.question)) score += 0.3; @@ -232,7 +232,7 @@ Use when the eval itself fails to execute. ```typescript agent.createEval({ metadata: { name: 'external-validation' }, - handler: async (c, input, output) => { + handler: async (ctx, input, output) => { try { const response = await fetch('https://api.example.com/validate', { method: 'POST', @@ -269,7 +269,7 @@ Eval handlers receive the same context (`EvalContext`) as agent handlers: ```typescript agent.createEval({ metadata: { name: 'comprehensive-tracker' }, - handler: async (c, input, output) => { + handler: async (ctx, input, output) => { const startTime = c.state.get('startTime') as number; const duration = Date.now() - startTime; @@ -287,7 +287,7 @@ agent.createEval({ }); // Log result - c.logger.info('Performance tracked', { sessionId: c.sessionId, duration }); + ctx.logger.info('Performance tracked', { sessionId: c.sessionId, duration }); return { success: true, @@ -311,7 +311,7 @@ const agent = createAgent({ keywords: z.array(z.string()) }), }, - handler: async (c, input) => { + handler: async (ctx, input) => { return { summary: generateSummary(input.text), keywords: extractKeywords(input.text) @@ -322,7 +322,7 @@ const agent = createAgent({ // Eval 1: Summary length agent.createEval({ metadata: { name: 'summary-length' }, - handler: async (c, input, output) => { + handler: async (ctx, input, output) => { const passed = output.summary.length >= 20 && output.summary.length <= 200; return { success: true, @@ -335,7 +335,7 @@ agent.createEval({ // Eval 2: Quality score agent.createEval({ metadata: { name: 'overall-quality' }, - handler: async (c, input, output) => { + handler: async (ctx, input, output) => { let score = 0; if (output.summary.length >= 50) score += 0.5; if (output.keywords.length >= 3) score += 0.5; @@ -386,7 +386,7 @@ const customerServiceAgent = createAgent({ actionsTaken: z.array(z.string()) }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { // Agent processes customer request return { response: "Your order has been cancelled and refund processed.", @@ -400,7 +400,7 @@ customerServiceAgent.createEval({ name: 'task-completion-check', description: 'Evaluates if agent successfully completed customer request' }, - handler: async (c, input, output) => { + handler: async (ctx, input, output) => { const { object } = await generateObject({ model: openai('gpt-5-nano'), schema: z.object({ @@ -450,7 +450,7 @@ const ragAgent = createAgent({ sources: z.array(z.string()) }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { // Retrieve relevant documents const results = await c.vector.search('knowledge-base', { query: input.question, @@ -475,7 +475,7 @@ ragAgent.createEval({ name: 'hallucination-check', description: 'Detects claims not supported by retrieved sources' }, - handler: async (c, input, output) => { + handler: async (ctx, input, output) => { // Access retrieved documents from handler execution const retrievedDocs = c.state.get('retrievedDocs') as string[]; @@ -520,7 +520,7 @@ Check for policy violations or sensitive content: ```typescript agent.createEval({ metadata: { name: 'content-safety' }, - handler: async (c, input, output) => { + handler: async (ctx, input, output) => { const profanityList = ['badword1', 'badword2']; const piiPatterns = { email: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/, @@ -571,7 +571,7 @@ const ragAgent = createAgent({ confidence: z.number() }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { const results = await c.vector.search('docs', { query: input.query, limit: 5, @@ -593,7 +593,7 @@ ragAgent.createEval({ name: 'contextual-relevancy', description: 'Evaluates if retrieved documents are relevant to query' }, - handler: async (c, input, output) => { + handler: async (ctx, input, output) => { const results = c.state.get('retrievedResults') as VectorSearchResult[]; const { object } = await generateObject({ @@ -626,7 +626,7 @@ ragAgent.createEval({ name: 'answer-relevancy', description: 'Evaluates if answer addresses the question' }, - handler: async (c, input, output) => { + handler: async (ctx, input, output) => { const { object } = await generateObject({ model: openai('gpt-5-nano'), schema: z.object({ @@ -655,7 +655,7 @@ ragAgent.createEval({ name: 'faithfulness', description: 'Checks if answer contains information not in sources' }, - handler: async (c, input, output) => { + handler: async (ctx, input, output) => { const results = c.state.get('retrievedResults') as VectorSearchResult[]; const sources = results.map(r => r.metadata?.text).join('\n\n'); @@ -695,7 +695,7 @@ Evals should handle errors gracefully to avoid breaking the eval pipeline. ```typescript agent.createEval({ metadata: { name: 'safe-external-check' }, - handler: async (c, input, output) => { + handler: async (ctx, input, output) => { try { const response = await fetch('https://api.example.com/validate', { method: 'POST', @@ -711,7 +711,7 @@ agent.createEval({ const result = await response.json(); return { success: true, passed: result.isValid }; } catch (error) { - c.logger.error('External validation failed', { error: error.message }); + ctx.logger.error('External validation failed', { error: error.message }); return { success: false, error: error.message }; } }, @@ -723,13 +723,13 @@ agent.createEval({ ```typescript agent.createEval({ metadata: { name: 'resilient-eval' }, - handler: async (c, input, output) => { + handler: async (ctx, input, output) => { // Try primary method try { const result = await validateWithLLM(output.answer); return { success: true, score: result.score, metadata: { method: 'llm' } }; } catch (error) { - c.logger.warn('LLM validation failed, using fallback'); + ctx.logger.warn('LLM validation failed, using fallback'); } // Fallback to rule-based @@ -792,7 +792,7 @@ agent.createEval({ // Good: Single-purpose agent.createEval({ metadata: { name: 'length-check' }, - handler: async (c, input, output) => { + handler: async (ctx, input, output) => { return { success: true, passed: output.answer.length >= 50 }; }, }); @@ -800,7 +800,7 @@ agent.createEval({ // Avoid: Multiple concerns in one eval agent.createEval({ metadata: { name: 'everything-check' }, - handler: async (c, input, output) => { + handler: async (ctx, input, output) => { const lengthOk = output.answer.length >= 50; const hasKeywords = checkKeywords(output); const sentiment = analyzeSentiment(output); @@ -861,7 +861,7 @@ Track execution time and resource usage: ```typescript agent.createEval({ metadata: { name: 'performance-monitor' }, - handler: async (c, input, output) => { + handler: async (ctx, input, output) => { const startTime = c.state.get('startTime') as number; const duration = startTime ? Date.now() - startTime : 0; @@ -889,7 +889,7 @@ Compare different approaches: ```typescript agent.createEval({ metadata: { name: 'ab-test-tracker' }, - handler: async (c, input, output) => { + handler: async (ctx, input, output) => { const variant = c.state.get('variant') as 'A' | 'B'; if (!variant) { return { success: false, error: 'No variant specified' }; diff --git a/content/v1/Guides/events.mdx b/content/v1/Guides/events.mdx index b6368985..284a992b 100644 --- a/content/v1/Guides/events.mdx +++ b/content/v1/Guides/events.mdx @@ -24,8 +24,8 @@ const agent = createAgent({ input: z.object({ task: z.string() }), output: z.object({ result: z.string() }) }, - handler: async (c, input) => { - c.logger.info('Processing task', { task: input.task }); + handler: async (ctx, input) => { + ctx.logger.info('Processing task', { task: input.task }); // Simulate processing await new Promise(resolve => setTimeout(resolve, 100)); @@ -36,7 +36,7 @@ const agent = createAgent({ // Track when agent starts agent.addEventListener('started', (eventName, agent, ctx) => { - c.state.set('startTime', Date.now()); + ctx.state.set('startTime', Date.now()); c.logger.info('Agent started', { agentName: agent.metadata.name, sessionId: c.sessionId @@ -93,7 +93,7 @@ const agent = createAgent({ confidence: z.number() }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { // Process query and return result return { answer: `Answer to: ${input.query}`, @@ -109,7 +109,7 @@ agent.addEventListener('completed', (eventName, agent, ctx) => { // Check confidence threshold if (output.confidence < 0.7) { - c.logger.warn('Low confidence output detected', { + ctx.logger.warn('Low confidence output detected', { confidence: output.confidence, threshold: 0.7, agentName: agent.metadata.name @@ -118,7 +118,7 @@ agent.addEventListener('completed', (eventName, agent, ctx) => { // Check answer length if (output.answer.length < 10) { - c.logger.warn('Suspiciously short answer', { + ctx.logger.warn('Suspiciously short answer', { answerLength: output.answer.length, agentName: agent.metadata.name }); @@ -276,11 +276,11 @@ Clean up resources when threads are destroyed: import { createAgent } from '@agentuity/runtime'; const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { // Register cleanup handler on first access if (!c.thread.state.has('cleanupRegistered')) { c.thread.addEventListener('destroyed', (eventName, thread) => { - c.logger.info('Cleaning up thread resources', { + ctx.logger.info('Cleaning up thread resources', { threadId: thread.id, messageCount: thread.state.get('messageCount') || 0 }); @@ -320,7 +320,7 @@ const agent = createAgent({ schema: { input: z.object({ action: z.string() }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { // Register session completion handler once if (!c.session.state.has('persistenceRegistered')) { c.session.addEventListener('completed', async (eventName, session) => { @@ -335,7 +335,7 @@ const agent = createAgent({ ttl: 86400 // 24 hours }); - c.logger.info('Session metrics saved', { + ctx.logger.info('Session metrics saved', { sessionId: session.id, metrics }); @@ -372,7 +372,7 @@ Track agent execution time and identify slow operations: import { createAgent } from '@agentuity/runtime'; const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { // Process request await new Promise(resolve => setTimeout(resolve, 1200)); return { result: 'completed' }; @@ -380,7 +380,7 @@ const agent = createAgent({ }); agent.addEventListener('started', (eventName, agent, ctx) => { - c.state.set('performanceStart', performance.now()); + ctx.state.set('performanceStart', performance.now()); }); agent.addEventListener('completed', (eventName, agent, ctx) => { @@ -389,7 +389,7 @@ agent.addEventListener('completed', (eventName, agent, ctx) => { // Log slow executions if (duration > 1000) { - c.logger.warn('Slow agent execution detected', { + ctx.logger.warn('Slow agent execution detected', { agentName: agent.metadata.name, duration, threshold: 1000, @@ -584,7 +584,7 @@ Use `c.waitUntil()` to perform heavy work without blocking the response: import { createAgent } from '@agentuity/runtime'; const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { return { result: 'processed' }; } }); @@ -595,7 +595,7 @@ agent.addEventListener('completed', (eventName, agent, ctx) => { // Simulate sending metrics to external service await new Promise(resolve => setTimeout(resolve, 500)); - c.logger.info('Metrics sent to external service', { + ctx.logger.info('Metrics sent to external service', { agentName: agent.metadata.name, sessionId: c.sessionId }); @@ -651,7 +651,7 @@ export default app.server; // In agent.ts const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { console.log(`[${Date.now()}] Agent: handler executing`); await new Promise(resolve => setTimeout(resolve, 100)); return { result: 'done' }; diff --git a/content/v1/Guides/key-value-storage.mdx b/content/v1/Guides/key-value-storage.mdx index d8d1133c..74d4c2f3 100644 --- a/content/v1/Guides/key-value-storage.mdx +++ b/content/v1/Guides/key-value-storage.mdx @@ -53,9 +53,9 @@ The SDK automatically creates key-value storage when you first access it: import { createAgent } from '@agentuity/runtime'; const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { // Buckets are auto-created if they don't exist - await c.kv.set('user-sessions', 'user-123', { + await ctx.kv.set('user-sessions', 'user-123', { lastSeen: new Date().toISOString(), preferences: { theme: 'dark' } }); @@ -77,23 +77,23 @@ Store strings, objects, or binary data. Keys persist indefinitely by default, or ```typescript const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { // Simple store - await c.kv.set('cache', 'api-response', responseData); + await ctx.kv.set('cache', 'api-response', responseData); // Store an object - await c.kv.set('user-prefs', input.userId, { + await ctx.kv.set('user-prefs', input.userId, { language: 'en', timezone: 'UTC' }); // Store with TTL (expires after 1 hour) - await c.kv.set('sessions', input.sessionId, userData, { + await ctx.kv.set('sessions', input.sessionId, userData, { ttl: 3600 // seconds }); // Store feature flags (no TTL - persistent config) - await c.kv.set('feature-flags', 'beta-features', { + await ctx.kv.set('feature-flags', 'beta-features', { darkMode: true, aiAssistant: false, newDashboard: true @@ -110,16 +110,16 @@ Retrieve stored values with automatic deserialization: ```typescript const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { // Get a value - const result = await c.kv.get('user-prefs', input.userId); + const result = await ctx.kv.get('user-prefs', input.userId); if (result.exists) { // Data is already deserialized const preferences = result.data; - c.logger.info('User preferences:', preferences); + ctx.logger.info('User preferences:', preferences); } else { - c.logger.info('No preferences found for user'); + ctx.logger.info('No preferences found for user'); } // Handle missing keys gracefully @@ -143,9 +143,9 @@ interface UserPreferences { } const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { // Specify type for type-safe access - const result = await c.kv.get('user-prefs', input.userId); + const result = await ctx.kv.get('user-prefs', input.userId); if (result.exists) { // TypeScript knows the shape of result.data @@ -164,11 +164,11 @@ Remove keys when they're no longer needed: ```typescript const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { // Delete a single key - await c.kv.delete('sessions', input.sessionId); + await ctx.kv.delete('sessions', input.sessionId); - c.logger.info('Session deleted successfully'); + ctx.logger.info('Session deleted successfully'); return { success: true }; } @@ -190,18 +190,18 @@ Always handle potential storage errors gracefully: ```typescript const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { try { - const result = await c.kv.get('config', 'settings'); + const result = await ctx.kv.get('config', 'settings'); if (!result.exists) { // Initialize with defaults - await c.kv.set('config', 'settings', defaultSettings); + await ctx.kv.set('config', 'settings', defaultSettings); } return { config: result.exists ? result.data : defaultSettings }; } catch (error) { - c.logger.error('Storage error:', error); + ctx.logger.error('Storage error:', error); // Fall back to in-memory defaults return { config: defaultSettings }; @@ -224,25 +224,25 @@ Example with appropriate TTLs: ```typescript const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { // Session data - expires after 24 hours - await c.kv.set('sessions', input.sessionId, sessionData, { + await ctx.kv.set('sessions', input.sessionId, sessionData, { ttl: 86400 }); // API cache - expires after 5 minutes - await c.kv.set('cache', cacheKey, apiResponse, { + await ctx.kv.set('cache', cacheKey, apiResponse, { ttl: 300 }); // Rate limit counter - expires at end of hour const secondsUntilHourEnd = 3600 - (Date.now() / 1000 % 3600); - await c.kv.set('rate-limits', input.userId, requestCount, { + await ctx.kv.set('rate-limits', input.userId, requestCount, { ttl: Math.ceil(secondsUntilHourEnd) }); // Feature flags - no TTL (persistent) - await c.kv.set('feature-flags', 'global', featureFlags); + await ctx.kv.set('feature-flags', 'global', featureFlags); return { success: true }; } diff --git a/content/v1/Guides/object-storage.mdx b/content/v1/Guides/object-storage.mdx index b32601fa..84854844 100644 --- a/content/v1/Guides/object-storage.mdx +++ b/content/v1/Guides/object-storage.mdx @@ -46,12 +46,12 @@ Buckets are automatically created when you first access them: import { createAgent } from '@agentuity/runtime'; const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { // TODO: Verify file upload input handling pattern const imageData = new Uint8Array(input.fileData); // Bucket 'user-uploads' is auto-created if it doesn't exist - await c.objectstore.put('user-uploads', 'profile-123.jpg', imageData, { + await ctx.objectstore.put('user-uploads', 'profile-123.jpg', imageData, { contentType: 'image/jpeg' }); @@ -72,14 +72,14 @@ Store files with optional metadata and HTTP headers: import { createAgent } from '@agentuity/runtime'; const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { // Store with content type - await c.objectstore.put('documents', 'report.pdf', pdfData, { + await ctx.objectstore.put('documents', 'report.pdf', pdfData, { contentType: 'application/pdf' }); // Store with full metadata - await c.objectstore.put('uploads', 'document.pdf', pdfData, { + await ctx.objectstore.put('uploads', 'document.pdf', pdfData, { contentType: 'application/pdf', contentDisposition: 'attachment; filename="report.pdf"', cacheControl: 'max-age=3600', @@ -91,7 +91,7 @@ const agent = createAgent({ // Store text file const textData = new TextEncoder().encode(logContent); - await c.objectstore.put('logs', 'agent.log', textData, { + await ctx.objectstore.put('logs', 'agent.log', textData, { contentType: 'text/plain', contentEncoding: 'utf-8' }); @@ -123,26 +123,26 @@ Retrieve stored objects with type-safe null checking: import { createAgent } from '@agentuity/runtime'; const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { // Get an object - const result = await c.objectstore.get('documents', 'report.pdf'); + const result = await ctx.objectstore.get('documents', 'report.pdf'); if (result.exists) { // Access binary data (Uint8Array) const pdfData = result.data; const contentType = result.contentType; - c.logger.info(`PDF size: ${pdfData.byteLength} bytes`); - c.logger.info(`Content type: ${contentType}`); + ctx.logger.info(`PDF size: ${pdfData.byteLength} bytes`); + ctx.logger.info(`Content type: ${contentType}`); } else { - c.logger.info('Object not found'); + ctx.logger.info('Object not found'); } // Convert to text if needed const textResult = await c.objectstore.get('logs', 'agent.log'); if (textResult.exists) { const logContent = new TextDecoder().decode(textResult.data); - c.logger.info('Log content:', logContent); + ctx.logger.info('Log content:', logContent); } return { found: result.exists }; @@ -168,21 +168,21 @@ Create time-limited public URLs for direct access to objects: import { createAgent } from '@agentuity/runtime'; const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { // Create a 1-hour public URL - const publicUrl = await c.objectstore.createPublicURL( + const publicUrl = await ctx.objectstore.createPublicURL( 'documents', 'report.pdf', { expiresDuration: 3600000 } // 1 hour in milliseconds ); - c.logger.info(`Download link: ${publicUrl}`); + ctx.logger.info(`Download link: ${publicUrl}`); // Create with default expiration (1 hour) - const imageUrl = await c.objectstore.createPublicURL('images', 'photo.jpg'); + const imageUrl = await ctx.objectstore.createPublicURL('images', 'photo.jpg'); // Create short-lived URL (minimum 1 minute) - const tempUrl = await c.objectstore.createPublicURL( + const tempUrl = await ctx.objectstore.createPublicURL( 'temp-files', 'preview.png', { expiresDuration: 60000 } @@ -208,14 +208,14 @@ Remove objects when they're no longer needed: import { createAgent } from '@agentuity/runtime'; const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { // Delete an object const wasDeleted = await c.objectstore.delete('temp-files', 'processing.tmp'); if (wasDeleted) { - c.logger.info('Temporary file cleaned up'); + ctx.logger.info('Temporary file cleaned up'); } else { - c.logger.info('File was already deleted'); + ctx.logger.info('File was already deleted'); } return { deleted: wasDeleted }; @@ -246,10 +246,10 @@ Use hierarchical paths for better organization: ```typescript const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { // Organize by user const key = `users/${input.userId}/profile.jpg`; - await c.objectstore.put('uploads', key, imageData, { + await ctx.objectstore.put('uploads', key, imageData, { contentType: 'image/jpeg' }); @@ -259,7 +259,7 @@ const agent = createAgent({ const month = String(date.getMonth() + 1).padStart(2, '0'); const documentKey = `documents/${year}/${month}/report-${input.reportId}.pdf`; - await c.objectstore.put('reports', documentKey, pdfData, { + await ctx.objectstore.put('reports', documentKey, pdfData, { contentType: 'application/pdf' }); @@ -289,12 +289,12 @@ const contentTypes: Record = { }; const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { const filename = input.filename; const extension = filename.substring(filename.lastIndexOf('.')); const contentType = contentTypes[extension] || 'application/octet-stream'; - await c.objectstore.put('uploads', filename, input.fileData, { + await ctx.objectstore.put('uploads', filename, input.fileData, { contentType }); @@ -309,7 +309,7 @@ Use appropriate expiration times for public URLs: ```typescript const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { let expiresDuration: number; switch (input.accessType) { @@ -351,10 +351,10 @@ Handle storage operations gracefully: import { createAgent } from '@agentuity/runtime'; const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { try { // Attempt to store file - await c.objectstore.put('uploads', input.key, input.data, { + await ctx.objectstore.put('uploads', input.key, input.data, { contentType: input.contentType }); @@ -370,7 +370,7 @@ const agent = createAgent({ url }; } catch (error) { - c.logger.error('Storage error:', error); + ctx.logger.error('Storage error:', error); return { success: false, @@ -406,13 +406,13 @@ const uploadAgent = createAgent({ url: z.string() }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { // Generate unique key const timestamp = Date.now(); const key = `uploads/${timestamp}-${input.filename}`; // Store the uploaded file - await c.objectstore.put('user-files', key, input.fileData, { + await ctx.objectstore.put('user-files', key, input.fileData, { contentType: input.contentType, metadata: { 'original-name': input.filename, @@ -451,10 +451,10 @@ const documentProcessorAgent = createAgent({ rawDocument: z.instanceof(Uint8Array) }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { // Store raw document const rawKey = `documents/raw/${input.documentId}.pdf`; - await c.objectstore.put('documents', rawKey, input.rawDocument, { + await ctx.objectstore.put('documents', rawKey, input.rawDocument, { contentType: 'application/pdf', metadata: { 'status': 'raw', @@ -469,7 +469,7 @@ const documentProcessorAgent = createAgent({ // Store processed version const processedKey = `documents/processed/${input.documentId}.txt`; - await c.objectstore.put('documents', processedKey, textData, { + await ctx.objectstore.put('documents', processedKey, textData, { contentType: 'text/plain', metadata: { 'status': 'processed', diff --git a/content/v1/Guides/routing-triggers.mdx b/content/v1/Guides/routing-triggers.mdx index 3d59cdef..c62387ba 100644 --- a/content/v1/Guides/routing-triggers.mdx +++ b/content/v1/Guides/routing-triggers.mdx @@ -143,13 +143,13 @@ Handle incoming emails sent to a specific address. ```typescript router.email('support@example.com', async (email, c) => { c.logger.info('Email received', { - from: email.from, - subject: email.subject + from: email.fromEmail(), + subject: email.subject() }); const result = await c.agent.emailProcessor.run({ - sender: email.from, - content: email.text || email.html || '' + sender: email.fromEmail() || 'unknown', + content: email.text() || email.html() || '' }); return c.json({ processed: true }); @@ -159,17 +159,30 @@ router.email('support@example.com', async (email, c) => { **Email object structure:** ```typescript interface Email { - from: string; - to: string[]; - subject: string; - text?: string; - html?: string; - headers: Record; - attachments?: Array<{ + // Sender information + fromEmail(): string | null; + fromName(): string | null; + + // Recipient information + to(): string | null; // All recipients (comma-separated) + toEmail(): string | null; // First recipient email + toName(): string | null; // First recipient name + + // Message content + subject(): string | null; + text(): string | null; + html(): string | null; + + // Attachments + attachments(): Array<{ filename: string; contentType: string; - content: Buffer; }>; + + // Metadata + date(): Date | null; + messageId(): string | null; + headers(): Headers; } ``` @@ -353,7 +366,7 @@ const agent = createAgent({ input: z.object({ message: z.string() }), output: z.string() }, - handler: async (c, input) => { + handler: async (ctx, input) => { return `Received: ${input.message}`; } }); @@ -394,6 +407,8 @@ The GET route provides an easy way to test the agent in a browser, while the POS The context object (`c`) provides access to request data and Agentuity services: +**Note:** Route handlers use Hono Context (often called `c`), which is different from the AgentContext used in agent handlers. See the [Context Types Guide](/v1/Guides/context-types) for details. + **Request data:** ```typescript c.req.json() // Parse JSON body diff --git a/content/v1/Guides/schema-validation.mdx b/content/v1/Guides/schema-validation.mdx index a6d2221f..29ce7978 100644 --- a/content/v1/Guides/schema-validation.mdx +++ b/content/v1/Guides/schema-validation.mdx @@ -76,10 +76,10 @@ const agent = createAgent({ }).optional(), }), }, - handler: async (c, input) => { + handler: async (ctx, input) => { // input is validated before this runs // TypeScript knows the exact shape of input - c.logger.info(`User email: ${input.email}`); + ctx.logger.info(`User email: ${input.email}`); return { success: true }; }, }); @@ -98,7 +98,7 @@ const agent = createAgent({ status: z.enum(['active', 'pending']), }), }, - handler: async (c, input) => { + handler: async (ctx, input) => { return { userId: crypto.randomUUID(), created: new Date(), @@ -125,7 +125,7 @@ const agent = createAgent({ total: z.number(), }), }, - handler: async (c, input) => { + handler: async (ctx, input) => { // Both input and output are validated return { results: ['item1', 'item2'], @@ -142,7 +142,7 @@ Schemas are optional. If not provided, no validation occurs: ```typescript const agent = createAgent({ // No schema defined - handler: async (c, input) => { + handler: async (ctx, input) => { // input is unknown type // No runtime validation return { message: 'Done' }; @@ -169,7 +169,7 @@ const agent = createAgent({ }), }), }, - handler: async (c, input) => { + handler: async (ctx, input) => { // TypeScript knows: // - input.query is string // - input.filters.category is 'tech' | 'business' | 'sports' @@ -195,7 +195,7 @@ const agent = createAgent({ total: z.number(), }), }, - handler: async (c, input) => { + handler: async (ctx, input) => { // Return type is validated and type-checked return { results: [ @@ -216,7 +216,7 @@ When calling agents, TypeScript knows the input and output types: ```typescript // When calling the agent from another agent: -const result = await c.agent.searchAgent.run({ +const result = await ctx.agent.searchAgent.run({ query: 'agentic AI', filters: { category: 'tech', limit: 5 }, }); @@ -406,9 +406,9 @@ const agent = createAgent({ count: z.number(), }), }, - handler: async (c, input) => { + handler: async (ctx, input) => { // Input validated before handler runs - c.logger.info('Processing query', { query: input.query }); + ctx.logger.info('Processing query', { query: input.query }); // Process the query... const results = [ @@ -508,14 +508,14 @@ const agent = createAgent({ schema: { input: z.object({ age: z.number().min(0) }), }, - handler: async (c, input) => { + handler: async (ctx, input) => { // This never runs if age is negative return { processed: true }; }, }); // This throws a validation error: -await c.agent.myAgent.run({ age: -1 }); +await ctx.agent.myAgent.run({ age: -1 }); ``` ### Output Validation Errors @@ -527,7 +527,7 @@ const agent = createAgent({ schema: { output: z.object({ status: z.enum(['ok', 'error']) }), }, - handler: async (c, input) => { + handler: async (ctx, input) => { return { status: 'invalid' }; // Validation error! }, }); @@ -634,7 +634,7 @@ const agent = createAgent({ age: z.number().min(0) }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { // input is guaranteed valid }, }); diff --git a/content/v1/Guides/sessions-threads.mdx b/content/v1/Guides/sessions-threads.mdx index 2b5e823c..584c3672 100644 --- a/content/v1/Guides/sessions-threads.mdx +++ b/content/v1/Guides/sessions-threads.mdx @@ -3,7 +3,7 @@ title: Sessions & Threads description: Stateful context management for conversational agents --- -Sessions and threads provide stateful context management at three different scopes: request-level for temporary data, thread-level for conversation context lasting up to 1 hour, and session-level for user data spanning multiple conversations. Use request state (`c.state`) for timing and calculations, thread state (`c.thread.state`) for chatbot memory, and session state (`c.session.state`) for user preferences and cross-conversation tracking. +Sessions and threads provide stateful context management at three different scopes: request-level for temporary data, thread-level for conversation context lasting up to 1 hour, and session-level for user data spanning multiple conversations. Use request state (`ctx.state`) for timing and calculations, thread state (`ctx.thread.state`) for chatbot memory, and session state (`ctx.session.state`) for user preferences and cross-conversation tracking. For event-based lifecycle management of sessions and threads, see the [Events Guide](/Guides/events). @@ -11,9 +11,9 @@ For event-based lifecycle management of sessions and threads, see the [Events Gu The SDK provides three distinct state scopes, each with different lifetimes and use cases: -- **Request state** (`c.state`) - Temporary data within a single request, cleared after response -- **Thread state** (`c.thread.state`) - Conversation context across multiple requests, expires after 1 hour -- **Session state** (`c.session.state`) - User-level data spanning multiple threads and conversations +- **Request state** (`ctx.state`) - Temporary data within a single request, cleared after response +- **Thread state** (`ctx.thread.state`) - Conversation context across multiple requests, expires after 1 hour +- **Session state** (`ctx.session.state`) - User-level data spanning multiple threads and conversations ### Three State Scopes in Action @@ -33,19 +33,19 @@ const agent = createAgent({ totalUserRequests: z.number() }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { // REQUEST STATE: Temporary data for this request only - c.state.set('requestStart', Date.now()); + ctx.state.set('requestStart', Date.now()); // THREAD STATE: Conversation history (persists up to 1 hour) const messages = (c.thread.state.get('messages') as string[]) || []; messages.push(input.message); - c.thread.state.set('messages', messages); + ctx.thread.state.set('messages', messages); // SESSION STATE: User-level data (persists across threads) const totalRequests = (c.session.state.get('totalRequests') as number) || 0; - c.session.state.set('totalRequests', totalRequests + 1); - c.session.state.set('lastMessage', input.message); + ctx.session.state.set('totalRequests', totalRequests + 1); + ctx.session.state.set('lastMessage', input.message); // Request state is used for response calculation const requestTime = Date.now() - (c.state.get('requestStart') as number); @@ -74,8 +74,8 @@ export default agent; | Scope | Lifetime | Cleared When | Use Case | Access | |-------|----------|--------------|----------|--------| -| Request | Single request | After response sent | Timing, temp calculations | `c.state` | -| Thread | Up to 1 hour | Thread expiration or destroy | Conversation history | `c.thread.state` | +| Request | Single request | After response sent | Timing, temp calculations | `ctx.state` | +| Thread | Up to 1 hour | Thread expiration or destroy | Conversation history | `ctx.thread.state` | | Session | Spans threads | In-memory (provider dependent) | User preferences | `c.session.state` | ## Thread Management @@ -122,7 +122,7 @@ const agent = createAgent({ source: z.enum(['kv', 'thread-state', 'new']) }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { // Reset conversation if requested if (input.reset) { await c.thread.destroy(); @@ -137,18 +137,18 @@ const agent = createAgent({ if (result.exists) { const saved = await result.data.json(); - c.thread.state.set('messages', saved.messages); + ctx.thread.state.set('messages', saved.messages); source = 'kv'; - c.logger.info('Loaded conversation from KV', { + ctx.logger.info('Loaded conversation from KV', { threadId: c.thread.id, messageCount: saved.messages.length }); } else { - c.thread.state.set('messages', []); + ctx.thread.state.set('messages', []); } // Register save handler when thread is destroyed - c.thread.addEventListener('destroyed', async (eventName, thread) => { + ctx.thread.addEventListener('destroyed', async (eventName, thread) => { const messages = thread.state.get('messages') as string[]; if (messages && messages.length > 0) { @@ -160,7 +160,7 @@ const agent = createAgent({ ttl: 86400 // Keep for 24 hours }); - c.logger.info('Saved conversation to KV', { + ctx.logger.info('Saved conversation to KV', { threadId: thread.id, messageCount: messages.length }); @@ -173,7 +173,7 @@ const agent = createAgent({ // Add message to thread state (fast access) const messages = c.thread.state.get('messages') as string[]; messages.push(input.message); - c.thread.state.set('messages', messages); + ctx.thread.state.set('messages', messages); return { response: `Stored message ${messages.length}`, @@ -225,11 +225,11 @@ Clean up resources when threads are destroyed (either by expiration or manual de import { createAgent } from '@agentuity/runtime'; const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { // Register cleanup handler once per thread if (!c.thread.state.has('cleanupRegistered')) { - c.thread.addEventListener('destroyed', async (eventName, thread) => { - c.logger.info('Thread destroyed - cleaning up', { + ctx.thread.addEventListener('destroyed', async (eventName, thread) => { + ctx.logger.info('Thread destroyed - cleaning up', { threadId: thread.id, messageCount: thread.state.get('messageCount') || 0, conversationDuration: Date.now() - (thread.state.get('startTime') as number || Date.now()) @@ -251,17 +251,17 @@ const agent = createAgent({ thread.state.clear(); }); - c.thread.state.set('cleanupRegistered', true); - c.thread.state.set('startTime', Date.now()); + ctx.thread.state.set('cleanupRegistered', true); + ctx.thread.state.set('startTime', Date.now()); } // Track messages const messageCount = (c.thread.state.get('messageCount') as number) || 0; - c.thread.state.set('messageCount', messageCount + 1); + ctx.thread.state.set('messageCount', messageCount + 1); const messages = (c.thread.state.get('messages') as string[]) || []; messages.push(JSON.stringify(input)); - c.thread.state.set('messages', messages); + ctx.thread.state.set('messages', messages); return { processed: true, messageCount: messageCount + 1 }; } @@ -303,7 +303,7 @@ const agent = createAgent({ }), stream: true }, - handler: async (c, input) => { + handler: async (ctx, input) => { const conversationKey = `chat_${c.thread.id}`; // Load conversation history from KV @@ -314,13 +314,13 @@ const agent = createAgent({ if (result.exists) { messages = await result.data.json() as CoreMessage[]; - c.logger.info('Loaded conversation from KV', { + ctx.logger.info('Loaded conversation from KV', { threadId: c.thread.id, messageCount: messages.length }); } } catch (error) { - c.logger.error('Error loading conversation history', { error }); + ctx.logger.error('Error loading conversation history', { error }); } // Add user message to conversation @@ -355,12 +355,12 @@ const agent = createAgent({ ttl: 86400 // 24 hours }); - c.logger.info('Saved conversation to KV', { + ctx.logger.info('Saved conversation to KV', { threadId: c.thread.id, messageCount: recentMessages.length }); } catch (error) { - c.logger.error('Error saving conversation history', { error }); + ctx.logger.error('Error saving conversation history', { error }); } }); @@ -406,7 +406,7 @@ const agent = createAgent({ source: z.enum(['cache', 'storage', 'default']) }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { const userId = input.userId; let source: 'cache' | 'storage' | 'default' = 'default'; @@ -433,13 +433,13 @@ const agent = createAgent({ } // Cache in session state - c.session.state.set(`preferences_${userId}`, preferences); + ctx.session.state.set(`preferences_${userId}`, preferences); } // Update if requested if (input.updatePreferences) { preferences = { ...preferences, ...input.updatePreferences }; - c.session.state.set(`preferences_${userId}`, preferences); + ctx.session.state.set(`preferences_${userId}`, preferences); // Persist to KV storage await c.kv.set('user-preferences', userId, preferences, { @@ -488,10 +488,10 @@ const agent = createAgent({ complete: z.boolean() }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { // Reset workflow if requested if (input.reset) { - c.thread.state.delete('workflowState'); + ctx.thread.state.delete('workflowState'); } // Initialize workflow @@ -556,7 +556,7 @@ const agent = createAgent({ } // Save workflow state - c.thread.state.set('workflowState', workflowState); + ctx.thread.state.set('workflowState', workflowState); return { message, @@ -577,7 +577,7 @@ Implement rate limiting per session: import { createAgent } from '@agentuity/runtime'; const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { const requestLimit = 10; const windowMs = 60000; // 1 minute @@ -593,7 +593,7 @@ const agent = createAgent({ const oldestRequest = Math.min(...recentRequests); const resetIn = windowMs - (now - oldestRequest); - c.logger.warn('Rate limit exceeded', { + ctx.logger.warn('Rate limit exceeded', { sessionId: c.sessionId, requestCount: recentRequests.length, resetInMs: resetIn @@ -609,7 +609,7 @@ const agent = createAgent({ // Add current request recentRequests.push(now); - c.session.state.set('requestLog', recentRequests); + ctx.session.state.set('requestLog', recentRequests); return { processed: true, @@ -660,7 +660,7 @@ const agent = createAgent({ profile: UserProfileSchema }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { const userId = input.userId; // Load profile from session state cache @@ -686,11 +686,11 @@ const agent = createAgent({ } // Cache in session state - c.session.state.set(`profile_${userId}`, profile); + ctx.session.state.set(`profile_${userId}`, profile); // Register session completion handler to save on exit if (!c.session.state.has('saveHandlerRegistered')) { - c.session.addEventListener('completed', async (eventName, session) => { + ctx.session.addEventListener('completed', async (eventName, session) => { const profileToSave = session.state.get(`profile_${userId}`) as typeof profile; if (profileToSave) { @@ -698,11 +698,11 @@ const agent = createAgent({ ttl: 2592000 // 30 days }); - c.logger.info('Saved user profile', { userId }); + ctx.logger.info('Saved user profile', { userId }); } }); - c.session.state.set('saveHandlerRegistered', true); + ctx.session.state.set('saveHandlerRegistered', true); } } @@ -710,7 +710,7 @@ const agent = createAgent({ if (input.updateProfile) { profile = { ...profile, ...input.updateProfile }; profile.lastActive = new Date().toISOString(); - c.session.state.set(`profile_${userId}`, profile); + ctx.session.state.set(`profile_${userId}`, profile); } return { profile }; @@ -744,7 +744,7 @@ Keep conversation history bounded to avoid memory issues: import { createAgent } from '@agentuity/runtime'; const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { const maxMessages = 50; // Keep last 50 messages in memory const archiveThreshold = 40; // Archive when reaching 40 messages @@ -769,7 +769,7 @@ const agent = createAgent({ ttl: 604800 // Keep for 7 days }); - c.logger.info('Archived old messages', { + ctx.logger.info('Archived old messages', { threadId: c.thread.id, archivedCount: toArchive.length }); @@ -777,9 +777,9 @@ const agent = createAgent({ // Keep only recent messages in state const recentMessages = messages.slice(-maxMessages); - c.thread.state.set('messages', recentMessages); + ctx.thread.state.set('messages', recentMessages); } else { - c.thread.state.set('messages', messages); + ctx.thread.state.set('messages', messages); } return { @@ -824,14 +824,14 @@ Demonstrate the relationship between thread and session IDs: import { createAgent } from '@agentuity/runtime'; const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { // Track unique sessions per thread const sessionsSeen = (c.thread.state.get('sessionsSeen') as Set) || new Set(); sessionsSeen.add(c.sessionId); - c.thread.state.set('sessionsSeen', sessionsSeen); + ctx.thread.state.set('sessionsSeen', sessionsSeen); // Log both IDs for correlation - c.logger.info('Request tracking', { + ctx.logger.info('Request tracking', { threadId: c.thread.id, // Same across conversation sessionId: c.sessionId, // Unique per request requestNumber: sessionsSeen.size, @@ -840,7 +840,7 @@ const agent = createAgent({ // Initialize thread tracking if (!c.thread.state.has('threadStartTime')) { - c.thread.state.set('threadStartTime', Date.now()); + ctx.thread.state.set('threadStartTime', Date.now()); } return { diff --git a/content/v1/Guides/subagents.mdx b/content/v1/Guides/subagents.mdx index edb54516..b7d352a8 100644 --- a/content/v1/Guides/subagents.mdx +++ b/content/v1/Guides/subagents.mdx @@ -3,7 +3,7 @@ title: Subagents description: Organize related agents into parent-child hierarchies --- -Subagents organize related agents into parent-child hierarchies with one level of nesting. Use subagents to group related functionality under a common parent, share validation logic, and create hierarchical API structures. Each subagent maintains its own handler and routes while accessing the parent agent via `c.parent`. +Subagents organize related agents into parent-child hierarchies with one level of nesting. Use subagents to group related functionality under a common parent, share validation logic, and create hierarchical API structures. Each subagent maintains its own handler and routes while accessing the parent agent via `ctx.parent`. For routing patterns and middleware configuration, see the [Routing & Triggers Guide](/Guides/routing-triggers). For general agent patterns, see [Core Concepts](/Introduction/core-concepts). @@ -80,15 +80,15 @@ const agent = createAgent({ taskCount: z.number().optional() }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { if (input.action === 'info') { return { message: 'Team management system' }; } // Coordinate between subagents const [members, tasks] = await Promise.all([ - c.agent.team.members.run({ action: 'count' }), - c.agent.team.tasks.run({ action: 'count' }) + ctx.agent.team.members.run({ action: 'count' }), + ctx.agent.team.tasks.run({ action: 'count' }) ]); return { @@ -108,7 +108,7 @@ export default agent; **Key Points:** - Parent is a standard agent created with `createAgent()` -- Can call subagents via `c.agent.parent.child.run()` +- Can call subagents via `ctx.agent.parent.child.run()` - Provides high-level coordination between subagents - Routes follow standard patterns (see [Routing & Triggers Guide](/Guides/routing-triggers)) @@ -124,8 +124,8 @@ Subagents are created by placing agent.ts and route.ts files in a subdirectory o **Naming Convention:** - Agent name: `parent.child` (e.g., `team.members`) -- Property access: `c.agent.team.members` (nested object) -- Current agent name: `c.agentName` returns `"team.members"` +- Property access: `ctx.agent.team.members` (nested object) +- Current agent name: `ctx.agentName` returns `"team.members"` ### Basic Subagent @@ -147,7 +147,7 @@ const agent = createAgent({ count: z.number().optional() }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { const result = await c.kv.get('team-data', 'members'); let members: string[] = result.exists && result.data ? result.data : []; @@ -171,7 +171,7 @@ export default agent; **Key Points:** - Same `createAgent()` syntax as regular agents - Agent name automatically becomes `team.members` -- Access via `c.agentName` returns `"team.members"` +- Access via `ctx.agentName` returns `"team.members"` - Full schema validation and type inference @@ -192,14 +192,14 @@ const router = createRouter(); // GET /agent/team/members router.get('/', async (c) => { - const result = await c.agent.team.members.run({ action: 'list' }); + const result = await ctx.agent.team.members.run({ action: 'list' }); return c.json(result); }); // POST /agent/team/members/add router.post('/add', zValidator('json', z.object({ name: z.string() })), async (c) => { const data = c.req.valid('json'); - const result = await c.agent.team.members.run({ action: 'add', name: data.name }); + const result = await ctx.agent.team.members.run({ action: 'add', name: data.name }); return c.json(result); }); @@ -208,13 +208,13 @@ export default router; **Key Points:** - Routes inherit parent path: `/agent/team/members` -- Access subagent via `c.agent.team.members.run()` +- Access subagent via `ctx.agent.team.members.run()` - Standard Hono routing patterns apply - See [Routing & Triggers Guide](/Guides/routing-triggers) for complete patterns ## Accessing Subagents -Subagents are accessed via nested properties on `c.agent` or `c.agent`. Type inference provides autocomplete and compile-time validation when schemas are defined. +Subagents are accessed via nested properties on `ctx.agent` or `ctx.agent`. Type inference provides autocomplete and compile-time validation when schemas are defined. ```typescript // From routes or other agents @@ -223,8 +223,8 @@ const router = createRouter(); router.get('/summary', async (c) => { // Parallel execution const [members, tasks] = await Promise.all([ - c.agent.team.members.run({ action: 'list' }), - c.agent.team.tasks.run({ action: 'list' }) + ctx.agent.team.members.run({ action: 'list' }), + ctx.agent.team.tasks.run({ action: 'list' }) ]); return c.json({ @@ -235,7 +235,7 @@ router.get('/summary', async (c) => { // From another agent const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { const members = await c.agent.team.members.run({ action: 'count' }); return { count: members.count }; } @@ -272,7 +272,7 @@ The `c.parent` property is available only in subagent handlers: ```typescript // team/members/agent.ts const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { let parentInfo: string | undefined; // Check if parent is available @@ -280,7 +280,7 @@ const agent = createAgent({ const parentData = await c.parent.run({ action: 'info' }); parentInfo = parentData.message; - c.logger.info('Retrieved parent context', { + ctx.logger.info('Retrieved parent context', { parentMessage: parentInfo, currentAgent: c.agentName // "team.members" }); @@ -315,7 +315,7 @@ const teamAgent = createAgent({ userRole: z.string() }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { const result = await c.kv.get('team-memberships', `${input.teamId}:${input.userId}`); if (!result.exists) { @@ -341,7 +341,7 @@ const teamAgent = createAgent({ // team/members/agent.ts - Uses parent validation const membersAgent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { // Validate with admin role requirement if (c.parent) { await c.parent.run({ @@ -462,7 +462,7 @@ const userAgent = createAgent({ valid: z.boolean().optional() }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { const result = await c.kv.get('users', input.userId); if (!result.exists) throw new Error('User not found'); @@ -499,7 +499,7 @@ const profileAgent = createAgent({ }) }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { // Validate user via parent if (c.parent) { const validation = await c.parent.run({ @@ -567,10 +567,10 @@ For complete working examples of these patterns, see the [Examples](/Examples) p ```typescript // Good: Parent coordinates const teamAgent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { const [members, tasks] = await Promise.all([ - c.agent.team.members.run({ action: 'count' }), - c.agent.team.tasks.run({ action: 'count' }) + ctx.agent.team.members.run({ action: 'count' }), + ctx.agent.team.tasks.run({ action: 'count' }) ]); return { memberCount: members.count, taskCount: tasks.count }; } @@ -578,7 +578,7 @@ const teamAgent = createAgent({ // Bad: Parent implements domain logic const teamAgent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { const members = await c.kv.get('members', 'list'); return { members: members.data.length }; // Should delegate to subagent } @@ -608,7 +608,7 @@ team/memberAdmins/agent.ts // Separate subagent **Cache parent context if calling multiple times:** ```typescript const membersAgent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { const parentData = c.parent ? await c.parent.run({ teamId: input.teamId }) : null; // Use parentData multiple times without re-calling return { processed: true }; @@ -668,7 +668,7 @@ describe('Members Subagent', () => { **Let validation errors propagate:** ```typescript const membersAgent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { if (c.parent) { await c.parent.run({ userId: input.userId, teamId: input.teamId }); // Parent throws on validation failure - error propagates to caller @@ -681,12 +681,12 @@ const membersAgent = createAgent({ **Graceful degradation for optional parent calls:** ```typescript const membersAgent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { if (c.parent) { try { await c.parent.run({ userId: input.userId }); } catch (error) { - c.logger.warn('Parent validation unavailable', { error }); + ctx.logger.warn('Parent validation unavailable', { error }); // Continue with degraded functionality } } diff --git a/content/v1/Introduction/architecture.mdx b/content/v1/Introduction/architecture.mdx index 47756046..286aa573 100644 --- a/content/v1/Introduction/architecture.mdx +++ b/content/v1/Introduction/architecture.mdx @@ -55,7 +55,7 @@ Agentuity consists of five primary components: ## Built-in Services -Agentuity includes built-in services that work out of the box. These services are accessible through the agent context (`c`) and require minimal configuration. +Agentuity includes built-in services that work out of the box. These services are accessible through the agent context (`ctx`) and require minimal configuration. ### Routing and Connectivity @@ -71,17 +71,17 @@ Agentuity includes built-in services that work out of the box. These services ar | Service | Description | Access | |---------|-------------|--------| -| Key-Value Storage | Fast key-value store with TTL support | `c.kv` | -| Vector Database | Embeddings storage and semantic search | `c.vector` | -| Object Storage | Blob and file storage with public URL generation | `c.objectstore` | -| Stream Storage | Large data streaming and processing | `c.stream` | +| Key-Value Storage | Fast key-value store with TTL support | `ctx.kv` | +| Vector Database | Embeddings storage and semantic search | `ctx.vector` | +| Object Storage | Blob and file storage with public URL generation | `ctx.objectstore` | +| Stream Storage | Large data streaming and processing | `ctx.stream` | ### Observability | Service | Description | Access | |---------|-------------|--------| -| Structured Logging | Built-in logger with contextual information | `c.logger` | -| OpenTelemetry Tracing | Distributed tracing for agent interactions | `c.tracer` | +| Structured Logging | Built-in logger with contextual information | `ctx.logger` | +| OpenTelemetry Tracing | Distributed tracing for agent interactions | `ctx.tracer` | | Real-time Analytics | Execution metrics, performance data, and usage tracking | Web Console | | Error Tracking | Automatic error capture and reporting | Web Console | @@ -342,8 +342,8 @@ export default createAgent({ name: 'My Agent', description: 'A simple agent' }, - handler: async (c, input) => { - c.logger.info('Processing request', { input }); + handler: async (ctx, input) => { + ctx.logger.info('Processing request', { input }); return { response: `Received: ${input.message}` @@ -494,7 +494,7 @@ agent.createEval({ name: 'quality-check', description: 'Validates output quality' }, - handler: async (c, input, output) => { + handler: async (ctx, input, output) => { // Evaluate the output const score = calculateQuality(output); @@ -524,7 +524,7 @@ const agent = createAgent({ greeting: z.string() }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { // input is fully typed and validated return { greeting: `Hello ${input.name}!` diff --git a/content/v1/Introduction/core-concepts.mdx b/content/v1/Introduction/core-concepts.mdx index 62669444..4bacb2d9 100644 --- a/content/v1/Introduction/core-concepts.mdx +++ b/content/v1/Introduction/core-concepts.mdx @@ -17,8 +17,8 @@ const agent = createAgent({ name: 'My Agent', description: 'A simple agent that greets users' }, - handler: async (c) => { - c.logger.info('Agent invoked'); + handler: async (ctx) => { + ctx.logger.info('Agent invoked'); return { message: 'Hello!' }; } }); @@ -56,7 +56,7 @@ const agent = createAgent({ greeting: z.string() }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { // input is fully typed and validated! return { greeting: `Hello ${input.name}!` @@ -154,7 +154,7 @@ const agent = createAgent({ schema: { input: z.object({ message: z.string() }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { // input is already parsed and validated return { echo: input.message }; } @@ -182,7 +182,7 @@ By default, returning an object will automatically serialize it as JSON. For mor **Simple Returns:** ```typescript -handler: async (c, input) => { +handler: async (ctx, input) => { return { message: 'Done!' }; // Automatically becomes JSON } ``` @@ -213,9 +213,9 @@ To call another agent, use `ctx.agent.otherAgent.run()`. This provides direct ac ```typescript const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { // Call another agent - const result = await c.agent.helperAgent.run({ + const result = await ctx.agent.helperAgent.run({ data: input.data }); @@ -239,16 +239,16 @@ Sessions track individual executions, while threads track conversation histories **Threads** represent conversation histories for multi-turn interactions. Use threads to maintain context across multiple requests. ```typescript -handler: async (c, input) => { +handler: async (ctx, input) => { // Log session info - c.logger.info('Session:', { - id: c.sessionId, - session: c.session + ctx.logger.info('Session:', { + id: ctx.sessionId, + session: ctx.session }); // Track conversation turns - const turnCount = (c.state.get('turnCount') as number || 0) + 1; - c.state.set('turnCount', turnCount); + const turnCount = (ctx.state.get('turnCount') as number || 0) + 1; + ctx.state.set('turnCount', turnCount); return { turn: turnCount, @@ -287,7 +287,7 @@ agent.createEval({ name: 'quality-check', description: 'Validates output quality' }, - handler: async (c, input, output) => { + handler: async (ctx, input, output) => { const score = calculateQuality(output); return { success: true, diff --git a/content/v1/SDK/api-reference.mdx b/content/v1/SDK/api-reference.mdx index 93b6aba4..0c7e05ba 100644 --- a/content/v1/SDK/api-reference.mdx +++ b/content/v1/SDK/api-reference.mdx @@ -1716,7 +1716,18 @@ export default router; ### Router Context -Router handlers receive a `c` parameter (short for "context") which provides access to the request, response helpers, and Agentuity services. This is distinct from the `ctx` parameter used in agent handlers. +Router handlers receive a context parameter (typically `c`) that provides access to the request, response helpers, and Agentuity services. This Hono Context is distinct from the `AgentContext` type used in agent handlers. + +#### Understanding Context Types + +Agentuity uses two distinct context types based on where you're writing code: + +- **AgentContext**: Used in `agent.ts` files for business logic (no HTTP access) +- **Router Context (Hono)**: Used in `route.ts` files for HTTP handling (has HTTP + agent services) + +Both commonly use `c` as the variable name in SDK examples. The distinction is **type-based**, not name-based. + +For a detailed explanation with examples, see the [Context Types Guide](/v1/Guides/context-types). **Router Context Interface:** @@ -1743,13 +1754,15 @@ interface RouterContext { } ``` -**Key Differences from Agent Context (`ctx`):** +**Key Differences Between Context Types:** -| Feature | Router Context (`c`) | Agent Context (`ctx`) | -|---------|---------------------|---------------------| +| Feature | Router Context (Hono) | Agent Context | +|---------|----------------------|---------------| +| **Type** | Hono Context | `AgentContext` | +| **Used in** | `route.ts` files | `agent.ts` files | | **Request access** | `c.req` (Hono Request) | Direct `input` parameter (validated) | | **Response** | Builder methods (`.json()`, `.text()`) | Direct returns | -| **Services** | Same (`.agent`, `.kv`, `.logger`, etc.) | Same | +| **Services** | `.agent`, `.kv`, `.logger`, etc. | `.agent`, `.kv`, `.logger`, etc. | | **State management** | Via Hono middleware | Built-in (`.state`, `.session`, `.thread`) | **Example Usage:** diff --git a/content/v1/SDK/core-concepts.mdx b/content/v1/SDK/core-concepts.mdx index 130e21a6..f6368ccd 100644 --- a/content/v1/SDK/core-concepts.mdx +++ b/content/v1/SDK/core-concepts.mdx @@ -26,7 +26,7 @@ const agent = createAgent({ name: 'Chat Agent', description: 'Processes chat messages' }, - handler: async (c, input) => { + handler: async (ctx, input) => { // Input is already validated and typed return { response: `You said: ${input.message}` }; } @@ -57,7 +57,7 @@ const agent = createAgent({ verified: z.boolean() }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { // input.email and input.age are fully typed // Return type is also validated return { @@ -83,7 +83,7 @@ import { createAgent } from '@agentuity/runtime'; export const chatAgent = createAgent({ schema: { /* ... */ }, - handler: async (c, input) => { /* ... */ } + handler: async (ctx, input) => { /* ... */ } }); ``` @@ -115,9 +115,9 @@ Agent handlers receive two parameters: Handlers return data directly. The SDK handles serialization and response formatting. ```typescript -handler: async (c, input) => { +handler: async (ctx, input) => { // Access context capabilities - c.logger.info('Processing request', { input }); + ctx.logger.info('Processing request', { input }); // Return data directly (no response object needed) return { success: true, data: processedData }; @@ -140,7 +140,7 @@ const agent = createAgent({ filters: z.array(z.string()).optional() }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { // input is validated and typed // TypeScript knows: input.query is string // TypeScript knows: input.filters is string[] | undefined @@ -163,7 +163,7 @@ const agent = createAgent({ data: z.any() }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { // Return must match output schema return { status: 'success', @@ -186,7 +186,7 @@ const agent = createAgent({ input: z.object({ prompt: z.string() }), stream: true }, - handler: async (c, input) => { + handler: async (ctx, input) => { const { textStream } = streamText({ model: openai('gpt-5-mini'), prompt: input.prompt @@ -203,17 +203,19 @@ See the [Schema Validation](/Guides/schema-validation) guide for advanced patter The context object provides access to all SDK capabilities within your handler. Properties are organized by function: +**Note:** This AgentContext is distinct from the Router Context used in `route.ts` files. See the [Context Types Guide](/v1/Guides/context-types) for the differences. + ### Identifiers Access unique identifiers for the current execution: ```typescript -handler: async (c, input) => { +handler: async (ctx, input) => { // Unique ID for this execution - const sessionId = c.sessionId; + const sessionId = ctx.sessionId; // Name of the current agent - const agentName = c.agentName; + const agentName = ctx.agentName; } ``` @@ -222,17 +224,17 @@ handler: async (c, input) => { Call other agents in your project or access agent references: ```typescript -handler: async (c, input) => { +handler: async (ctx, input) => { // Call another agent (type-safe) - const result = await c.agent.enrichmentAgent.run({ + const result = await ctx.agent.enrichmentAgent.run({ text: input.data }); // Reference to current agent - const self = c.current; + const self = ctx.current; // Parent agent reference (in subagents) - const parent = c.parent; + const parent = ctx.parent; if (parent) { const parentResult = await parent.run(input); } @@ -246,25 +248,25 @@ Agent calls are type-safe when both agents have schemas. See [Agent Communicatio Access state at three different scopes: ```typescript -handler: async (c, input) => { +handler: async (ctx, input) => { // Request scope - cleared after response - c.state.set('startTime', Date.now()); + ctx.state.set('startTime', Date.now()); // Thread scope - conversation context (1 hour lifetime) - const history = c.thread.state.get('messages') || []; - c.thread.state.set('messages', [...history, input.message]); + const history = ctx.thread.state.get('messages') || []; + ctx.thread.state.set('messages', [...history, input.message]); // Session scope - spans threads - const userPrefs = c.session.state.get('preferences'); - c.session.state.set('lastActive', new Date()); + const userPrefs = ctx.session.state.get('preferences'); + ctx.session.state.set('lastActive', new Date()); } ``` | Scope | Lifetime | Use Case | |-------|----------|----------| -| `c.state` | Single request | Timing, temporary calculations | -| `c.thread.state` | Up to 1 hour | Conversation history | -| `c.session.state` | Spans threads | User preferences, settings | +| `ctx.state` | Single request | Timing, temporary calculations | +| `ctx.thread.state` | Up to 1 hour | Conversation history | +| `ctx.session.state` | Spans threads | User preferences, settings | See the [Sessions and Threads](/Guides/sessions-threads) guide for detailed state management patterns. @@ -273,25 +275,25 @@ See the [Sessions and Threads](/Guides/sessions-threads) guide for detailed stat Access persistent storage systems: ```typescript -handler: async (c, input) => { +handler: async (ctx, input) => { // Key-value storage - await c.kv.set('cache', 'user-123', userData, { ttl: 3600 }); - const cached = await c.kv.get('cache', 'user-123'); + await ctx.kv.set('cache', 'user-123', userData, { ttl: 3600 }); + const cached = await ctx.kv.get('cache', 'user-123'); // Vector storage - const results = await c.vector.search('products', { + const results = await ctx.vector.search('products', { query: input.searchTerm, limit: 5, similarity: 0.7 }); // Object storage - await c.objectstore.put('uploads', 'file.pdf', fileData, { + await ctx.objectstore.put('uploads', 'file.pdf', fileData, { contentType: 'application/pdf' }); // Stream storage - const stream = await c.stream.create('export', { + const stream = await ctx.stream.create('export', { contentType: 'text/csv' }); } @@ -310,13 +312,13 @@ See the [API Reference](/SDK/api-reference) for complete storage API documentati Monitor and trace agent execution: ```typescript -handler: async (c, input) => { +handler: async (ctx, input) => { // Structured logging - c.logger.info('Processing request', { input }); - c.logger.error('Operation failed', { error: err }); + ctx.logger.info('Processing request', { input }); + ctx.logger.error('Operation failed', { error: err }); // OpenTelemetry tracing - const span = c.tracer.startSpan('database-query'); + const span = ctx.tracer.startSpan('database-query'); // ... perform operation span.end(); } @@ -327,14 +329,14 @@ handler: async (c, input) => { Execute background tasks that continue after the response is sent: ```typescript -handler: async (c, input) => { +handler: async (ctx, input) => { // Process data immediately const result = processData(input); // Run analytics in background - c.waitUntil(async () => { + ctx.waitUntil(async () => { await trackAnalytics(result); - await updateMetrics(c.sessionId); + await updateMetrics(ctx.sessionId); }); // Response sent immediately, background tasks continue @@ -380,12 +382,11 @@ The router provides methods for event-driven and scheduled triggers: ```typescript // Email handling -router.email('support@example.com', async (c) => { - const email = c.req.email; +router.email('support@example.com', async (email, c) => { const result = await c.agent.emailAgent.run({ - from: email.from, - subject: email.subject, - body: email.body + from: email.fromEmail() || 'unknown', + subject: email.subject() || 'no subject', + body: email.text() || email.html() || '' }); return c.json(result); }); @@ -438,13 +439,13 @@ Agents can call other agents in the same project using type-safe references thro ### Calling Other Agents -Use `c.agent.agentName.run()` to invoke another agent: +Use `ctx.agent.agentName.run()` to invoke another agent: ```typescript const coordinatorAgent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { // Call another agent - const enriched = await c.agent.enrichmentAgent.run({ + const enriched = await ctx.agent.enrichmentAgent.run({ text: input.rawData }); @@ -461,10 +462,10 @@ When both agents have schemas, the call is fully type-safe. TypeScript will vali Execute agents sequentially when each depends on the previous result: ```typescript -handler: async (c, input) => { - const step1 = await c.agent.validator.run(input); - const step2 = await c.agent.enricher.run(step1); - const step3 = await c.agent.analyzer.run(step2); +handler: async (ctx, input) => { + const step1 = await ctx.agent.validator.run(input); + const step2 = await ctx.agent.enricher.run(step1); + const step3 = await ctx.agent.analyzer.run(step2); return step3; } @@ -473,11 +474,11 @@ handler: async (c, input) => { Execute agents in parallel when operations are independent: ```typescript -handler: async (c, input) => { +handler: async (ctx, input) => { const [webResults, dbResults, cacheResults] = await Promise.all([ - c.agent.webSearch.run(input), - c.agent.database.run(input), - c.agent.cache.run(input) + ctx.agent.webSearch.run(input), + ctx.agent.database.run(input), + ctx.agent.cache.run(input) ]); return { webResults, dbResults, cacheResults }; @@ -490,12 +491,12 @@ Access subagents using the nested path syntax: ```typescript // Call a subagent from anywhere -const result = await c.agent.team.members.run({ +const result = await ctx.agent.team.members.run({ action: 'list' }); // From a subagent, access the parent -const parentResult = await c.parent.run(input); +const parentResult = await ctx.parent.run(input); ``` Subagents organize related agents into parent-child hierarchies with one level of nesting. For detailed patterns and limitations, see the [Subagents](/Guides/subagents) guide. diff --git a/content/v1/SDK/error-handling.mdx b/content/v1/SDK/error-handling.mdx index e7b75366..590f7ab3 100644 --- a/content/v1/SDK/error-handling.mdx +++ b/content/v1/SDK/error-handling.mdx @@ -35,14 +35,14 @@ const agent = createAgent({ data: z.any() }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { // Throw errors when business logic fails if (input.operation === 'create' && !input.data) { throw new Error('Data is required for create operation'); } // Throw errors for authorization failures - const user = await getUserById(c.sessionId); + const user = await getUserById(ctx.sessionId); if (!user.canPerform(input.operation)) { throw new Error('Unauthorized to perform this operation'); } @@ -60,12 +60,12 @@ When agents call other agents, errors propagate automatically: ```typescript const coordinatorAgent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { // If validatorAgent throws an error, it propagates here - const validation = await c.agent.validatorAgent.run(input); + const validation = await ctx.agent.validatorAgent.run(input); // If enrichmentAgent throws an error, it propagates here - const enriched = await c.agent.enrichmentAgent.run(validation); + const enriched = await ctx.agent.enrichmentAgent.run(validation); return enriched; } @@ -80,14 +80,14 @@ For optional operations, use try-catch to handle errors gracefully: ```typescript const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { let enrichedData = input.data; // Try optional enrichment, but continue if it fails try { - enrichedData = await c.agent.enrichmentAgent.run(input); + enrichedData = await ctx.agent.enrichmentAgent.run(input); } catch (error) { - c.logger.warn('Enrichment failed, using original data', { + ctx.logger.warn('Enrichment failed, using original data', { error: error instanceof Error ? error.message : String(error) }); // Continue with original data @@ -125,7 +125,7 @@ class AuthorizationError extends Error { } const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { const user = await getUser(input.userId); if (!user) { @@ -259,7 +259,7 @@ const agent = createAgent({ role: z.enum(['user', 'admin']) }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { // If we reach this point, input is guaranteed to be valid // - input.email is a valid email address // - input.age is >= 18 @@ -284,7 +284,7 @@ const agent = createAgent({ data: z.any().optional() }) }, - handler: async (c, input) => { + handler: async (ctx, input) => { // This return value is validated return { status: 'success', @@ -307,15 +307,15 @@ Storage operations can fail due to network issues, timeouts, or service unavaila ```typescript const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { try { - const cached = await c.kv.get('cache', input.key); + const cached = await ctx.kv.get('cache', input.key); if (cached.exists) { return { source: 'cache', data: cached.data }; } } catch (error) { - c.logger.warn('Cache lookup failed, falling back to database', { + ctx.logger.warn('Cache lookup failed, falling back to database', { error: error instanceof Error ? error.message : String(error) }); } @@ -325,9 +325,9 @@ const agent = createAgent({ // Try to cache for next time (don't fail if this errors) try { - await c.kv.set('cache', input.key, data, { ttl: 3600 }); + await ctx.kv.set('cache', input.key, data, { ttl: 3600 }); } catch (error) { - c.logger.warn('Failed to cache result', { error }); + ctx.logger.warn('Failed to cache result', { error }); } return { source: 'database', data }; @@ -339,9 +339,9 @@ const agent = createAgent({ ```typescript const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { try { - const results = await c.vector.search('products', { + const results = await ctx.vector.search('products', { query: input.searchTerm, limit: 5, similarity: 0.7 @@ -349,7 +349,7 @@ const agent = createAgent({ return { results }; } catch (error) { - c.logger.error('Vector search failed', { + ctx.logger.error('Vector search failed', { error: error instanceof Error ? error.message : String(error) }); @@ -365,13 +365,13 @@ const agent = createAgent({ ```typescript const agent = createAgent({ - handler: async (c, input) => { + handler: async (ctx, input) => { try { - await c.objectstore.put('uploads', input.filename, input.data, { + await ctx.objectstore.put('uploads', input.filename, input.data, { contentType: input.contentType }); - const url = await c.objectstore.createPublicURL( + const url = await ctx.objectstore.createPublicURL( 'uploads', input.filename, 3600000 // 1 hour @@ -379,7 +379,7 @@ const agent = createAgent({ return { success: true, url }; } catch (error) { - c.logger.error('Failed to upload file', { + ctx.logger.error('Failed to upload file', { filename: input.filename, error: error instanceof Error ? error.message : String(error) }); @@ -406,7 +406,7 @@ const agent = createAgent({ name: 'Processing Agent', description: 'Processes user data' }, - handler: async (c, input) => { + handler: async (ctx, input) => { // Handler logic return { success: true }; } @@ -415,9 +415,9 @@ const agent = createAgent({ // Listen for errors agent.addEventListener('errored', (event, agent, ctx, error) => { // Log error with context - c.logger.error('Agent execution failed', { + ctx.logger.error('Agent execution failed', { agentName: agent.metadata.name, - sessionId: c.sessionId, + sessionId: ctx.sessionId, error: error.message, stack: error.stack }); @@ -425,7 +425,7 @@ agent.addEventListener('errored', (event, agent, ctx, error) => { // Send to error tracking service trackError({ agent: agent.metadata.name, - session: c.sessionId, + session: ctx.sessionId, error: error.message }); }); @@ -446,19 +446,19 @@ Use the logger to track execution flow and identify issues: ```typescript const agent = createAgent({ - handler: async (c, input) => { - c.logger.info('Processing started', { input }); + handler: async (ctx, input) => { + ctx.logger.info('Processing started', { input }); try { const step1 = await performStep1(input); - c.logger.debug('Step 1 completed', { step1 }); + ctx.logger.debug('Step 1 completed', { step1 }); const step2 = await performStep2(step1); - c.logger.debug('Step 2 completed', { step2 }); + ctx.logger.debug('Step 2 completed', { step2 }); return { result: step2 }; } catch (error) { - c.logger.error('Processing failed', { + ctx.logger.error('Processing failed', { error: error instanceof Error ? error.message : String(error), stack: error instanceof Error ? error.stack : undefined }); @@ -481,8 +481,8 @@ Use tracing to debug complex workflows: ```typescript const agent = createAgent({ - handler: async (c, input) => { - const span = c.tracer.startSpan('process-data'); + handler: async (ctx, input) => { + const span = ctx.tracer.startSpan('process-data'); try { span.setAttribute('input.size', JSON.stringify(input).length); @@ -574,7 +574,7 @@ throw new Error('Unable to authenticate user'); try { await authenticateUser(token); } catch (error) { - c.logger.error('Authentication failed', { + ctx.logger.error('Authentication failed', { error: error.message, token: token.substring(0, 8) + '...' // Partially redacted }); diff --git a/content/v1/Training/developers/01-intro-to-agents.mdx b/content/v1/Training/developers/01-intro-to-agents.mdx index b2de96b3..5cdebaa4 100644 --- a/content/v1/Training/developers/01-intro-to-agents.mdx +++ b/content/v1/Training/developers/01-intro-to-agents.mdx @@ -239,7 +239,7 @@ export const agent = createAgent({ }, // Handler receives (c, input) - input is already parsed and validated! - handler: async (c, input) => { + handler: async (ctx, input) => { const name = input.name || 'World'; // Return data directly - no response.json() needed @@ -277,7 +277,7 @@ router.post('/greet', async (c) => { - **Handler signature**: `(c, input)` instead of v0's `(request, response, context)` - **Input is pre-validated** by schema (no manual parsing needed) - **Direct return values** (not `response.json()`) -- **Routes use `c.agent.helloAgent.run()`** for type-safe agent calls +- **Routes use `ctx.agent.helloAgent.run()`** for type-safe agent calls **Try it:** 1. Create a new project: `npx @agentuity/cli create` @@ -309,7 +309,7 @@ This makes agents reusable - the same agent can be called from HTTP routes, cron -Add observability with `c.logger` to track what your agent is doing. Logs appear automatically in DevMode and the dashboard. +Add observability with `ctx.logger` to track what your agent is doing. Logs appear automatically in DevMode and the dashboard. ### agent.ts @@ -326,14 +326,14 @@ export const agent = createAgent({ name: 'Hello Agent', description: 'Greeting agent with logging' }, - handler: async (c, input) => { + handler: async (ctx, input) => { // Log when agent starts - c.logger.info('Hello agent received a request'); + ctx.logger.info('Hello agent received a request'); const name = input.name || 'World'; // Log the specific action - c.logger.info(`Greeting ${name}`); + ctx.logger.info(`Greeting ${name}`); return { message: `Hello, ${name}!` }; } @@ -355,7 +355,7 @@ router.post('/greet', async (c) => { ``` **What this demonstrates:** -- Using `c.logger.info()` for observability +- Using `ctx.logger.info()` for observability - Logs appear in DevMode console and dashboard - Structured logging helps debug and monitor agents @@ -378,7 +378,7 @@ router.post('/greet', async (c) => { -Use `c.kv` for persistent storage to make your agent remember state across invocations. This greeting agent will count how many times it's been called. +Use `ctx.kv` for persistent storage to make your agent remember state across invocations. This greeting agent will count how many times it's been called. We're using KV storage here to make our agent stateful. In **Module 3**, you'll learn about different memory scopes (request/thread/session), and in **Module 4**, we'll explore all storage APIs (KV, Vector, Object) in depth. @@ -403,8 +403,8 @@ export const agent = createAgent({ name: 'Hello Agent', description: 'Greeting agent with memory' }, - handler: async (c, input) => { - c.logger.info('Hello agent received a request'); + handler: async (ctx, input) => { + ctx.logger.info('Hello agent received a request'); const name = input.name || 'World'; @@ -424,7 +424,7 @@ export const agent = createAgent({ // Update the counter in storage await c.kv.set('stats', 'greeting_count', count); - c.logger.info(`Greeting #${count} for ${name}`); + ctx.logger.info(`Greeting #${count} for ${name}`); return { message: `Hello, ${name}!`, @@ -473,9 +473,9 @@ router.post('/greet', async (c) => { **KV Storage Quick Reference** ```typescript -c.kv.get(bucket, key) // Retrieve data -c.kv.set(bucket, key, value) // Store data -c.kv.delete(bucket, key) // Remove data + ctx.kv.get(bucket, key) // Retrieve data + ctx.kv.set(bucket, key, value) // Store data + ctx.kv.delete(bucket, key) // Remove data ``` We'll cover TTL (auto-expiration) in Module 3! @@ -508,12 +508,12 @@ export const agent = createAgent({ name: 'Hello Agent', description: 'Greeting agent with manual validation' }, - handler: async (c, input) => { - c.logger.info('Hello agent received a request'); + handler: async (ctx, input) => { + ctx.logger.info('Hello agent received a request'); // Manual validation (even though schema handles it!) if (input.name !== undefined && input.name.trim().length === 0) { - c.logger.warn('Empty name provided'); + ctx.logger.warn('Empty name provided'); throw new Error('Name cannot be empty string'); } @@ -529,11 +529,11 @@ export const agent = createAgent({ } await c.kv.set('stats', 'greeting_count', count); } catch (error) { - c.logger.error('Storage error, using default count', { error }); + ctx.logger.error('Storage error, using default count', { error }); // Continue with count = 1 instead of failing } - c.logger.info(`Greeting #${count} for ${name}`); + ctx.logger.info(`Greeting #${count} for ${name}`); return { message: `Hello, ${name}!`, @@ -566,10 +566,10 @@ export const agent = createAgent({ name: 'Hello Agent', description: 'Greeting agent with schema validation' }, - handler: async (c, input) => { + handler: async (ctx, input) => { // No manual validation needed - schema rejects invalid input before handler runs - c.logger.info('Hello agent received a request'); + ctx.logger.info('Hello agent received a request'); const name = input.name || 'World'; @@ -583,10 +583,10 @@ export const agent = createAgent({ } await c.kv.set('stats', 'greeting_count', count); } catch (error) { - c.logger.error('Storage error, using default count', { error }); + ctx.logger.error('Storage error, using default count', { error }); } - c.logger.info(`Greeting #${count} for ${name}`); + ctx.logger.info(`Greeting #${count} for ${name}`); return { message: `Hello, ${name}!`, diff --git a/content/v1/migration-guide.mdx b/content/v1/migration-guide.mdx index 9b87cf6b..76f9a1f7 100644 --- a/content/v1/migration-guide.mdx +++ b/content/v1/migration-guide.mdx @@ -310,18 +310,18 @@ import { createRouter } from '@agentuity/runtime'; const router = createRouter(); -router.post('/my-agent', async (ctx) => { +router.post('/my-agent', async (c) => { // Get JSON body directly from Hono context - const data = await ctx.req.json(); + const data = await c.req.json(); // Get text body - const text = await ctx.req.text(); + const text = await c.req.text(); // Get headers - const userId = ctx.req.header('x-user-id'); + const userId = c.req.header('x-user-id'); // Get query params - const param = ctx.req.query('param'); + const param = c.req.query('param'); return { processed: data }; }); From e71d249a8bb42b810276eb14b7b6c3b595a45f6a Mon Sep 17 00:00:00 2001 From: parteeksingh24 Date: Wed, 26 Nov 2025 10:28:58 -0800 Subject: [PATCH 11/63] Update docs structure --- content/v1/Building/Agents/ai-gateway.mdx | 179 ++++++ .../v1/Building/Agents/ai-sdk-integration.mdx | 247 ++++++++ .../Building/Agents/calling-other-agents.mdx | 569 ++++++++++++++++++ .../v1/Building/Agents/creating-agents.mdx | 255 ++++++++ content/v1/Building/Agents/evaluations.mdx | 308 ++++++++++ .../v1/Building/Agents/events-lifecycle.mdx | 161 +++++ content/v1/Building/Agents/meta.json | 15 + .../v1/Building/Agents/schema-libraries.mdx | 202 +++++++ .../v1/Building/Agents/state-management.mdx | 355 +++++++++++ .../Building/Agents/streaming-responses.mdx | 226 +++++++ content/v1/Building/Agents/subagents.mdx | 425 +++++++++++++ content/v1/Building/Routes-Triggers/cron.mdx | 119 ++++ content/v1/Building/Routes-Triggers/email.mdx | 129 ++++ .../Building/Routes-Triggers/http-routes.mdx | 206 +++++++ content/v1/Building/Routes-Triggers/meta.json | 11 + content/v1/Building/Routes-Triggers/sms.mdx | 127 ++++ content/v1/Building/Routes-Triggers/sse.mdx | 218 +++++++ .../Building/Routes-Triggers/websockets.mdx | 181 ++++++ content/v1/Building/meta.json | 4 + .../v1/Getting-Started/app-configuration.mdx | 122 ++++ content/v1/Getting-Started/installation.mdx | 48 ++ content/v1/Getting-Started/meta.json | 10 + .../v1/Getting-Started/project-structure.mdx | 119 ++++ content/v1/Getting-Started/quickstart.mdx | 144 +++++ .../v1/Getting-Started/what-is-agentuity.mdx | 66 ++ content/v1/Guides/vector-storage.mdx | 2 +- .../agent-communication.mdx | 0 .../architecture.mdx | 0 .../v1/{Guides => archive}/context-types.mdx | 0 .../v1/{Guides => archive}/evaluations.mdx | 0 content/v1/{Guides => archive}/events.mdx | 0 .../introduction.mdx | 0 .../{Guides => archive}/routing-triggers.mdx | 0 .../{Guides => archive}/schema-validation.mdx | 0 .../{Guides => archive}/sessions-threads.mdx | 0 .../subagents-old.mdx} | 0 content/v1/meta.json | 8 +- 37 files changed, 4449 insertions(+), 7 deletions(-) create mode 100644 content/v1/Building/Agents/ai-gateway.mdx create mode 100644 content/v1/Building/Agents/ai-sdk-integration.mdx create mode 100644 content/v1/Building/Agents/calling-other-agents.mdx create mode 100644 content/v1/Building/Agents/creating-agents.mdx create mode 100644 content/v1/Building/Agents/evaluations.mdx create mode 100644 content/v1/Building/Agents/events-lifecycle.mdx create mode 100644 content/v1/Building/Agents/meta.json create mode 100644 content/v1/Building/Agents/schema-libraries.mdx create mode 100644 content/v1/Building/Agents/state-management.mdx create mode 100644 content/v1/Building/Agents/streaming-responses.mdx create mode 100644 content/v1/Building/Agents/subagents.mdx create mode 100644 content/v1/Building/Routes-Triggers/cron.mdx create mode 100644 content/v1/Building/Routes-Triggers/email.mdx create mode 100644 content/v1/Building/Routes-Triggers/http-routes.mdx create mode 100644 content/v1/Building/Routes-Triggers/meta.json create mode 100644 content/v1/Building/Routes-Triggers/sms.mdx create mode 100644 content/v1/Building/Routes-Triggers/sse.mdx create mode 100644 content/v1/Building/Routes-Triggers/websockets.mdx create mode 100644 content/v1/Building/meta.json create mode 100644 content/v1/Getting-Started/app-configuration.mdx create mode 100644 content/v1/Getting-Started/installation.mdx create mode 100644 content/v1/Getting-Started/meta.json create mode 100644 content/v1/Getting-Started/project-structure.mdx create mode 100644 content/v1/Getting-Started/quickstart.mdx create mode 100644 content/v1/Getting-Started/what-is-agentuity.mdx rename content/v1/{Guides => archive}/agent-communication.mdx (100%) rename content/v1/{Introduction => archive}/architecture.mdx (100%) rename content/v1/{Guides => archive}/context-types.mdx (100%) rename content/v1/{Guides => archive}/evaluations.mdx (100%) rename content/v1/{Guides => archive}/events.mdx (100%) rename content/v1/{Introduction => archive}/introduction.mdx (100%) rename content/v1/{Guides => archive}/routing-triggers.mdx (100%) rename content/v1/{Guides => archive}/schema-validation.mdx (100%) rename content/v1/{Guides => archive}/sessions-threads.mdx (100%) rename content/v1/{Guides/subagents.mdx => archive/subagents-old.mdx} (100%) diff --git a/content/v1/Building/Agents/ai-gateway.mdx b/content/v1/Building/Agents/ai-gateway.mdx new file mode 100644 index 00000000..4fdac380 --- /dev/null +++ b/content/v1/Building/Agents/ai-gateway.mdx @@ -0,0 +1,179 @@ +--- +title: Using the AI Gateway +description: Automatic LLM routing with observability and cost tracking +--- + +# Using the AI Gateway + +Agentuity's AI Gateway routes LLM requests through a managed infrastructure, giving you unified observability and cost tracking across all model providers. + +## How It Works + +When you make LLM requests from your agents, they're *automatically* routed through the AI Gateway: + +``` +Your Agent → AI Gateway → Provider API (OpenAI, Anthropic, etc.) +``` + +The AI Gateway provides: + +- **Consolidated billing** across all LLM providers +- **Automatic observability** with token tracking and latency metrics +- **Request logging** visible in the Agentuity console +- **No configuration required** when using your SDK key + +## Using the AI Gateway + +The AI Gateway works automatically—whether you use the Vercel AI SDK, provider SDKs directly (Anthropic, OpenAI), or frameworks like Mastra and LangGraph. + +### With the Vercel AI SDK + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { generateText } from 'ai'; +import { openai } from '@ai-sdk/openai'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ prompt: z.string() }), + output: z.object({ response: z.string() }), + }, + handler: async (ctx, input) => { + // Requests route through AI Gateway automatically + const { text } = await generateText({ + model: openai('gpt-5-mini'), + prompt: input.prompt, + }); + + return { response: text }; + }, +}); + +export default agent; +``` + +### Multiple Providers + +You can easily compare responses from different providers by swapping out the `model` parameter: + +```typescript +import { createRouter } from '@agentuity/runtime'; +import { openai } from '@ai-sdk/openai'; +import { google } from '@ai-sdk/google'; +import { generateText } from 'ai'; + +const router = createRouter(); + +router.post('/compare', async (c) => { + const prompt = await c.req.text(); + + // Both providers route through AI Gateway automatically + const [resultOpenAI, resultGoogle] = await Promise.all([ + generateText({ + model: openai('gpt-5-mini'), + prompt, + }), + generateText({ + model: google('gemini-2.5-flash'), + prompt, + }), + ]); + + return c.json({ + openai: resultOpenAI.text, + google: resultGoogle.text, + }); +}); + +export default router; +``` + +### With Provider SDKs Directly + +You can also use provider SDKs directly. The AI Gateway routes these requests automatically: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import Anthropic from '@anthropic-ai/sdk'; +import { z } from 'zod'; + +const client = new Anthropic(); + +const agent = createAgent({ + schema: { + input: z.object({ prompt: z.string() }), + output: z.object({ response: z.string() }), + }, + handler: async (ctx, input) => { + const result = await client.messages.create({ + model: 'claude-sonnet-4-5', + max_tokens: 1024, + messages: [{ role: 'user', content: input.prompt }], + }); + + const text = result.content[0]?.type === 'text' + ? result.content[0].text + : ''; + + return { response: text }; + }, +}); + +export default agent; +``` + +## Supported Providers + +The following are some of the providers available through the AI Gateway: + +| Provider | Package | Example Models | +|----------|---------|----------------| +| OpenAI | `@ai-sdk/openai` | `gpt-5-mini`, `gpt-5` | +| Anthropic | `@ai-sdk/anthropic` | `claude-sonnet-4-5`, `claude-haiku-4-5` | +| Google | `@ai-sdk/google` | `gemini-2.5-pro`, `gemini-2.5-flash` | +| xAI | `@ai-sdk/xai` | `grok-3`, `grok-3-mini` | +| DeepSeek | `@ai-sdk/deepseek` | `deepseek-chat`, `deepseek-reasoner` | +| Groq | `@ai-sdk/groq` | `llama-3-70b`, `mixtral-8x7b` | +| Mistral | `@ai-sdk/mistral` | `mistral-large`, `mistral-small` | + +### Provider Imports + +```typescript +import { openai } from '@ai-sdk/openai'; +import { anthropic } from '@ai-sdk/anthropic'; +import { google } from '@ai-sdk/google'; +import { xai } from '@ai-sdk/xai'; +import { deepseek } from '@ai-sdk/deepseek'; +import { groq } from '@ai-sdk/groq'; +import { mistral } from '@ai-sdk/mistral'; +``` + +## BYO API Keys + +You can bypass the AI Gateway and use your own API keys, by updating your `.env` file: + +```bash +OPENAI_API_KEY=sk-... +ANTHROPIC_API_KEY=sk-ant-... +GOOGLE_GENERATIVE_AI_API_KEY=... +``` + +When these variables are set, requests go directly to the provider instead of through the AI Gateway. + +## Gateway vs BYO Keys + +| Aspect | AI Gateway | BYO API Keys | +|--------|------------|--------------| +| **Setup** | Just SDK key | Manage per-provider keys | +| **Cost tracking** | Automatic in console | Manual | +| **Observability** | Built-in token/latency metrics | Must configure separately | +| **Rate limits** | Shared pool | Your own limits | + +We recommend using the AI Gateway for most projects. + +## Next Steps + +- [Using the AI SDK](/Building/Agents/ai-sdk-integration): Structured output, tool calling, and multi-turn conversations +- [Returning Streaming Responses](/Building/Agents/streaming-responses): Real-time chat UIs and progress indicators +- [Logging](/Building/Observability/logging): Debug requests and track LLM performance diff --git a/content/v1/Building/Agents/ai-sdk-integration.mdx b/content/v1/Building/Agents/ai-sdk-integration.mdx new file mode 100644 index 00000000..1efa4315 --- /dev/null +++ b/content/v1/Building/Agents/ai-sdk-integration.mdx @@ -0,0 +1,247 @@ +--- +title: Using the AI SDK +description: Generate text, structured data, and streams with the Vercel AI SDK +--- + +# Using the AI SDK + +The [Vercel AI SDK](https://ai-sdk.dev) provides a consistent API for LLM interactions with built-in streaming, structured output, and tool calling. + +Agentuity works with any approach—you can also use provider SDKs directly (Anthropic, OpenAI), or frameworks like Mastra and LangGraph. See [Using the AI Gateway](/Building/Agents/ai-gateway) for examples with different libraries. + +## Installation + +Install the AI SDK and your preferred provider: + +```bash +bun add ai @ai-sdk/openai +``` + +Or with multiple providers: + +```bash +bun add ai @ai-sdk/openai @ai-sdk/anthropic @ai-sdk/google +``` + +## Generating Text + +Use `generateText` for simple completions: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { generateText } from 'ai'; +import { openai } from '@ai-sdk/openai'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ prompt: z.string() }), + output: z.object({ response: z.string() }), + }, + handler: async (ctx, input) => { + const { text } = await generateText({ + model: openai('gpt-5-mini'), + prompt: input.prompt, + }); + + return { response: text }; + }, +}); + +export default agent; +``` + +### With System Prompt + +Add context with a system message: + +```typescript +const { text } = await generateText({ + model: openai('gpt-5-mini'), + system: 'You are a concise technical assistant. Keep responses under 100 words.', + prompt: input.prompt, +}); +``` + +### With Message History + +Pass conversation history for multi-turn interactions: + +```typescript +const { text } = await generateText({ + model: openai('gpt-5-mini'), + messages: [ + { role: 'system', content: 'You are a helpful assistant.' }, + { role: 'user', content: 'What is TypeScript?' }, + { role: 'assistant', content: 'TypeScript is a typed superset of JavaScript.' }, + { role: 'user', content: input.followUp }, + ], +}); +``` + +## Generating Structured Data + +Use `generateObject` to get validated, typed responses: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { generateObject } from 'ai'; +import { openai } from '@ai-sdk/openai'; +import { z } from 'zod'; + +const SentimentSchema = z.object({ + sentiment: z.enum(['positive', 'negative', 'neutral']), + confidence: z.number().min(0).max(1), + keywords: z.array(z.string()), +}); + +const agent = createAgent({ + schema: { + input: z.object({ text: z.string() }), + output: SentimentSchema, + }, + handler: async (ctx, input) => { + const { object } = await generateObject({ + model: openai('gpt-5-mini'), + schema: SentimentSchema, + prompt: `Analyze the sentiment of: "${input.text}"`, + }); + + // object is fully typed as { sentiment, confidence, keywords } + return object; + }, +}); + +export default agent; +``` + + +Define your schema once and reuse it for both `generateObject` and your agent's output schema. This keeps types consistent throughout your codebase. + + +### Using `.describe()` for Field Hints + +Add `.describe()` to schema fields to guide the model on format and content: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { generateObject } from 'ai'; +import { openai } from '@ai-sdk/openai'; +import { z } from 'zod'; + +const EventSchema = z.object({ + title: z.string().describe('Event title, e.g. "Team standup"'), + date: z.string().describe('ISO 8601 date: YYYY-MM-DD'), + startTime: z.string().describe('24-hour format: HH:MM'), + duration: z.number().describe('Duration in minutes'), + attendees: z.array(z.string()).describe('List of attendee names'), +}); + +const agent = createAgent({ + schema: { + input: z.object({ text: z.string() }), + output: EventSchema, + }, + handler: async (ctx, input) => { + const { object } = await generateObject({ + model: openai('gpt-5-mini'), + schema: EventSchema, + prompt: `Extract event details from: "${input.text}"`, + }); + + return object; + }, +}); + +export default agent; +``` + +The `.describe()` hints improve output consistency, especially for dates, times, and formatted strings. + +## Generating Streams + +Use `streamText` for real-time responses: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { streamText } from 'ai'; +import { openai } from '@ai-sdk/openai'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ prompt: z.string() }), + stream: true, + }, + handler: async (ctx, input) => { + const { textStream } = streamText({ + model: openai('gpt-5-mini'), + prompt: input.prompt, + }); + + return textStream; + }, +}); + +export default agent; +``` + +For detailed streaming patterns, see [Streaming Responses](/Building/Agents/streaming-responses). + +## Provider Configuration + +### Switching Providers + +Change providers by swapping the import and model: + +```typescript +// OpenAI +import { openai } from '@ai-sdk/openai'; +const model = openai('gpt-5-mini'); + +// Anthropic +import { anthropic } from '@ai-sdk/anthropic'; +const model = anthropic('claude-sonnet-4-5'); + +// Google +import { google } from '@ai-sdk/google'; +const model = google('gemini-2.5-flash'); + +// Groq (fast inference) +import { groq } from '@ai-sdk/groq'; +const model = groq('llama-3-70b'); +``` + +## Error Handling + +Wrap LLM calls in try-catch to handle errors gracefully: + +```typescript +handler: async (ctx, input) => { + try { + const { text } = await generateText({ + model: openai('gpt-5-mini'), + prompt: input.prompt, + }); + + return { response: text }; + } catch (error) { + ctx.logger.error('LLM request failed', { error }); + + // Return fallback response + return { response: 'I encountered an error processing your request.' }; + } +} +``` + +## Best Practices + +- **Define output schemas** with [Zod](https://zod.dev), [Valibot](https://valibot.dev), or [ArkType](https://arktype.io) for type safety and validation +- **Add system prompts** to guide model behavior consistently +- **Handle errors gracefully** with fallback responses + +## Next Steps + +- [Using the AI Gateway](/Building/Agents/ai-gateway): Observability, cost tracking, and provider switching +- [Returning Streaming Responses](/Building/Agents/streaming-responses): Chat UIs and long-form content generation +- [Evaluations](/Building/Agents/evaluations): Quality checks and output validation diff --git a/content/v1/Building/Agents/calling-other-agents.mdx b/content/v1/Building/Agents/calling-other-agents.mdx new file mode 100644 index 00000000..0029577d --- /dev/null +++ b/content/v1/Building/Agents/calling-other-agents.mdx @@ -0,0 +1,569 @@ +--- +title: Calling Other Agents +description: Build multi-agent systems with type-safe agent-to-agent communication +--- + +# Calling Other Agents + +Break complex tasks into focused, reusable agents that communicate with type safety. Instead of building one large agent, create specialized agents that each handle a single responsibility. + +## Basic Usage + +Call other agents using `ctx.agent` in your handler: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const coordinator = createAgent({ + schema: { + input: z.object({ text: z.string() }), + output: z.object({ result: z.string() }), + }, + handler: async (ctx, input) => { + // Call another agent by name + const enriched = await ctx.agent.enrichmentAgent.run({ + text: input.text, + }); + + return { result: enriched.enrichedText }; + }, +}); + +export default coordinator; +``` + +When both agents have schemas, TypeScript validates the input and infers the output type automatically. + + +Define schemas on all agents to enable full type inference. TypeScript will validate that inputs match expected types and provide autocomplete for outputs. + + +## Communication Patterns + +### Sequential Execution + +Process data through a series of agents where each step depends on the previous result: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const pipeline = createAgent({ + schema: { + input: z.object({ rawData: z.string() }), + output: z.object({ processed: z.any() }), + }, + handler: async (ctx, input) => { + // Each step depends on the previous result + const validated = await ctx.agent.validatorAgent.run({ + data: input.rawData, + }); + + const enriched = await ctx.agent.enrichmentAgent.run({ + data: validated.cleanData, + }); + + const analyzed = await ctx.agent.analysisAgent.run({ + data: enriched.enrichedData, + }); + + return { processed: analyzed }; + }, +}); + +export default pipeline; +``` + +Errors propagate automatically. If `validatorAgent` throws, subsequent agents never execute. + +### Parallel Execution + +Run multiple agents simultaneously when their operations are independent: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const searchAgent = createAgent({ + schema: { + input: z.object({ query: z.string() }), + output: z.object({ results: z.array(z.any()) }), + }, + handler: async (ctx, input) => { + // Execute all searches in parallel + const [webResults, dbResults, vectorResults] = await Promise.all([ + ctx.agent.webSearchAgent.run({ query: input.query }), + ctx.agent.databaseAgent.run({ query: input.query }), + ctx.agent.vectorSearchAgent.run({ query: input.query }), + ]); + + return { + results: [...webResults.items, ...dbResults.items, ...vectorResults.items], + }; + }, +}); + +export default searchAgent; +``` + +If each agent takes 1 second, parallel execution completes in 1 second instead of 3. + +### Background Execution + +Use `ctx.waitUntil()` for fire-and-forget operations that continue after returning a response: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const processor = createAgent({ + schema: { + input: z.object({ data: z.any() }), + output: z.object({ status: z.string(), id: z.string() }), + }, + handler: async (ctx, input) => { + const id = crypto.randomUUID(); + + // Start background processing + ctx.waitUntil(async () => { + await ctx.agent.analyticsAgent.run({ + event: 'processed', + data: input.data, + }); + ctx.logger.info('Background processing completed', { id }); + }); + + // Return immediately + return { status: 'accepted', id }; + }, +}); + +export default processor; +``` + + +Use `ctx.waitUntil()` for analytics, logging, notifications, or any operation where the caller doesn't need the result. + + +### Conditional Routing + +Use an LLM to classify intent and route to the appropriate agent: + +```typescript +handler: async (ctx, input) => { + // Classify with a fast model, using Groq (via AI Gateway) + const { object: intent } = await generateObject({ + model: groq('llama-3.3-70b'), + schema: z.object({ + agentType: z.enum(['support', 'sales', 'technical']), + }), + prompt: input.message, + }); + + // Route based on classification + switch (intent.agentType) { + case 'support': + return ctx.agent.supportAgent.run(input); + case 'sales': + return ctx.agent.salesAgent.run(input); + case 'technical': + return ctx.agent.technicalAgent.run(input); + } +} +``` + +See [Full Example](#full-example) below for a complete implementation with error handling and logging. + +### Orchestrator Pattern + +An orchestrator is a coordinator agent that delegates work to specialized agents and combines their results. This pattern is useful for: + +- Multi-step content pipelines (generate → evaluate → refine) +- Parallel data gathering from multiple sources +- Workflows requiring different expertise (writer + reviewer + formatter) + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const orchestrator = createAgent({ + schema: { + input: z.object({ topic: z.string() }), + output: z.object({ content: z.string(), score: z.number() }), + }, + handler: async (ctx, input) => { + // Step 1: Generate content + const draft = await ctx.agent.writerAgent.run({ prompt: input.topic }); + + // Step 2: Evaluate quality + const evaluation = await ctx.agent.evaluatorAgent.run({ + content: draft.text, + }); + + // Step 3: Return combined result + return { content: draft.text, score: evaluation.score }; + }, +}); + +export default orchestrator; +``` + + +The orchestrator pattern is common in AI workflows where you want to separate concerns (generation, evaluation, formatting) into focused agents. You'll see this pattern in many of our [multi-agent examples](/Learning/Examples). + + +## Public Agents + + +Agent-to-agent calls via `ctx.agent` only work within the same project. To call agents in other projects or organizations, use `fetch()` with the agent's public URL. + + +Call public agents using standard HTTP: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ query: z.string() }), + output: z.object({ result: z.string() }), + }, + handler: async (ctx, input) => { + try { + const response = await fetch( + 'https://agentuity.ai/api/agent-id-here', + { + method: 'POST', + body: JSON.stringify({ query: input.query }), + headers: { 'Content-Type': 'application/json' }, + } + ); + + if (!response.ok) { + throw new Error(`Public agent returned ${response.status}`); + } + + const data = await response.json(); + return { result: data.response }; + } catch (error) { + ctx.logger.error('Public agent call failed', { error }); + throw error; + } + }, +}); + +export default agent; +``` + +## Error Handling + +### Cascading Failures + +By default, errors propagate through the call chain: + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + // If validatorAgent throws, execution stops here + const validated = await ctx.agent.validatorAgent.run(input); + + // This never executes if validation fails + const processed = await ctx.agent.processorAgent.run(validated); + + return processed; + }, +}); +``` + +This is the recommended pattern for critical operations where later steps cannot proceed without earlier results. + +### Graceful Degradation + +For optional operations, catch errors and continue: + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + let enrichedData = input.data; + + // Try to enrich, but continue if it fails + try { + const enrichment = await ctx.agent.enrichmentAgent.run({ + data: input.data, + }); + enrichedData = enrichment.data; + } catch (error) { + ctx.logger.warn('Enrichment failed, using original data', { + error: error instanceof Error ? error.message : String(error), + }); + } + + // Process with enriched data (or original if enrichment failed) + return await ctx.agent.processorAgent.run({ data: enrichedData }); + }, +}); +``` + +### Retry Pattern + +Implement retry logic for unreliable operations: + +```typescript +async function callWithRetry( + fn: () => Promise, + maxRetries: number = 3, + delayMs: number = 1000 +): Promise { + for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + return await fn(); + } catch (error) { + if (attempt === maxRetries) throw error; + + // Exponential backoff + const delay = delayMs * Math.pow(2, attempt - 1); + await new Promise((resolve) => setTimeout(resolve, delay)); + } + } + throw new Error('Retry failed'); +} + +const agent = createAgent({ + handler: async (ctx, input) => { + const result = await callWithRetry(() => + ctx.agent.externalServiceAgent.run(input) + ); + return result; + }, +}); +``` + +### Partial Failure Handling + +Handle mixed success/failure results with `Promise.allSettled()`: + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + const results = await Promise.allSettled( + input.items.map((item) => ctx.agent.processingAgent.run({ item })) + ); + + const successful = results + .filter((r) => r.status === 'fulfilled') + .map((r) => r.value); + + const failed = results + .filter((r) => r.status === 'rejected') + .map((r) => r.reason); + + if (failed.length > 0) { + ctx.logger.warn('Some operations failed', { failedCount: failed.length }); + } + + return { successful, failedCount: failed.length }; + }, +}); +``` + +## Best Practices + +### Keep Agents Focused + +Each agent should have a single, well-defined responsibility: + +```typescript +// Good: focused agents +const validatorAgent = createAgent({ /* validates data */ }); +const enrichmentAgent = createAgent({ /* enriches data */ }); +const analysisAgent = createAgent({ /* analyzes data */ }); + +// Bad: monolithic agent +const megaAgent = createAgent({ + handler: async (ctx, input) => { + // Validates, enriches, analyzes all in one place + }, +}); +``` + +Focused agents are easier to test, reuse, and maintain. + +### Use Schemas for Type Safety + +Define schemas on all agents for type-safe communication. See [Creating Agents](/Building/Agents/creating-agents) to learn more about using schemas. + +```typescript +// Source agent with output schema +const sourceAgent = createAgent({ + schema: { + output: z.object({ + data: z.string(), + metadata: z.object({ timestamp: z.string() }), + }), + }, + handler: async (ctx, input) => { + return { + data: 'result', + metadata: { timestamp: new Date().toISOString() }, + }; + }, +}); + +// Consumer agent - TypeScript validates the connection +const consumerAgent = createAgent({ + handler: async (ctx, input) => { + const result = await ctx.agent.sourceAgent.run({}); + // TypeScript knows result.data and result.metadata.timestamp exist + return { processed: result.data }; + }, +}); +``` + +### Leverage Shared Context + +Agent calls share the same session context: + +```typescript +const coordinator = createAgent({ + handler: async (ctx, input) => { + // Store data in thread state + ctx.thread.state.set('userId', input.userId); + + // Called agents can access the same thread state + const result = await ctx.agent.processingAgent.run(input); + + // All agents share sessionId + ctx.logger.info('Processing complete', { sessionId: ctx.sessionId }); + + return result; + }, +}); +``` + +Use this for tracking context, sharing auth data, and maintaining conversation state. + +## Full Example + +A customer support router that combines multiple patterns: conditional routing, graceful degradation, and background analytics. + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { generateObject } from 'ai'; +import { groq } from '@ai-sdk/groq'; +import { z } from 'zod'; + +const IntentSchema = z.object({ + agentType: z.enum(['support', 'sales', 'billing', 'general']), + confidence: z.number().min(0).max(1), + reasoning: z.string(), +}); + +const router = createAgent({ + schema: { + input: z.object({ message: z.string() }), + output: z.object({ + response: z.string(), + handledBy: z.string(), + }), + }, + handler: async (ctx, input) => { + let intent: z.infer; + let handledBy = 'general'; + + // Classify intent with graceful degradation + try { + const result = await generateObject({ + model: groq('llama-3.3-70b'), + schema: IntentSchema, + system: 'Classify the customer message by intent.', + prompt: input.message, + temperature: 0, + }); + intent = result.object; + + ctx.logger.info('Intent classified', { + type: intent.agentType, + confidence: intent.confidence, + }); + } catch (error) { + // Fallback to general agent if classification fails + ctx.logger.warn('Classification failed, using fallback', { + error: error instanceof Error ? error.message : String(error), + }); + intent = { agentType: 'general', confidence: 0, reasoning: 'fallback' }; + } + + // Route to specialist agent + let response: string; + try { + switch (intent.agentType) { + case 'support': + const supportResult = await ctx.agent.supportAgent.run({ + message: input.message, + context: intent.reasoning, + }); + response = supportResult.response; + handledBy = 'support'; + break; + + case 'sales': + const salesResult = await ctx.agent.salesAgent.run({ + message: input.message, + context: intent.reasoning, + }); + response = salesResult.response; + handledBy = 'sales'; + break; + + case 'billing': + const billingResult = await ctx.agent.billingAgent.run({ + message: input.message, + context: intent.reasoning, + }); + response = billingResult.response; + handledBy = 'billing'; + break; + + default: + const generalResult = await ctx.agent.generalAgent.run({ + message: input.message, + }); + response = generalResult.response; + handledBy = 'general'; + } + } catch (error) { + ctx.logger.error('Specialist agent failed', { error, intent }); + response = 'I apologize, but I encountered an issue. Please try again.'; + handledBy = 'error'; + } + + // Log analytics in background (doesn't block response) + ctx.waitUntil(async () => { + await ctx.agent.analyticsAgent.run({ + event: 'customer_interaction', + intent: intent.agentType, + confidence: intent.confidence, + handledBy, + sessionId: ctx.sessionId, + }); + }); + + return { response, handledBy }; + }, +}); + +export default router; +``` + +This example combines several patterns: +- Use an LLM to classify intent and route to specialist agents +- Handle failures gracefully — fallback to general agent if classification fails, friendly error message if specialists fail +- Log analytics in the background with `waitUntil()` so the response isn't delayed + +## Next Steps + +- [Subagents](/Building/Agents/subagents): Organize related agents into parent-child hierarchies +- [State Management](/Building/Agents/state-management): Share data across agent calls with thread and session state +- [Evaluations](/Building/Agents/evaluations): Add quality checks to your agent workflows diff --git a/content/v1/Building/Agents/creating-agents.mdx b/content/v1/Building/Agents/creating-agents.mdx new file mode 100644 index 00000000..14bea6e1 --- /dev/null +++ b/content/v1/Building/Agents/creating-agents.mdx @@ -0,0 +1,255 @@ +--- +title: Creating Agents +description: Build agents with createAgent(), schemas, and handlers +--- + +# Creating Agents + +Each agent encapsulates a handler function, input/output validation, and metadata in a single unit that can be invoked from routes, other agents, or scheduled tasks. + +## Basic Agent + +Create an agent with `createAgent()` and a handler function: + +```typescript +import { createAgent, type AgentContext } from '@agentuity/runtime'; + +const agent = createAgent({ + handler: async (ctx: AgentContext, input) => { + ctx.logger.info('Processing request', { input }); + return { message: 'Hello from agent!' }; + }, +}); + +export default agent; +``` + +The handler receives two parameters: +- `ctx` - The agent context with logging, storage, and state management +- `input` - The data passed to the agent (validated if schema is defined) + +## Adding Schema Validation + +Define input and output schemas for type safety and runtime validation using [Zod](https://zod.dev): + +```typescript +import { createAgent, type AgentContext } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ + email: z.string().email(), + message: z.string().min(1), + }), + output: z.object({ + success: z.boolean(), + id: z.string(), + }), + }, + handler: async (ctx: AgentContext, input) => { + // input is typed as { email: string, message: string } + ctx.logger.info('Received message', { from: input.email }); + + return { + success: true, + id: crypto.randomUUID(), + }; + }, +}); + +export default agent; +``` + +**Validation behavior:** +- Input is validated before the handler runs +- Output is validated before returning to the caller +- Invalid data throws an error with details about what failed + + +Agentuity supports any library implementing StandardSchema, including [Valibot](https://valibot.dev) and [ArkType](https://arktype.io). See [Schema Libraries](/Building/Agents/schema-libraries) for examples with alternative libraries. + + +### Type Inference + +TypeScript automatically infers types from your schemas: + +```typescript +const agent = createAgent({ + schema: { + input: z.object({ + query: z.string(), + filters: z.object({ + category: z.enum(['tech', 'business', 'sports']), + limit: z.number().default(10), + }), + }), + output: z.object({ + results: z.array(z.string()), + total: z.number(), + }), + }, + handler: async (ctx, input) => { + // Full autocomplete for input.query, input.filters.category, etc. + const category = input.filters.category; // type: 'tech' | 'business' | 'sports' + + return { + results: ['result1', 'result2'], + total: 2, + }; + }, +}); +``` + +### Common Zod Patterns + +```typescript +z.object({ + // Strings + name: z.string().min(1).max(100), + email: z.string().email(), + url: z.string().url().optional(), + + // Numbers + age: z.number().min(0).max(120), + score: z.number().min(0).max(1), + + // Enums and literals + status: z.enum(['active', 'pending', 'complete']), + type: z.literal('user'), + + // Arrays and nested objects + tags: z.array(z.string()), + metadata: z.object({ + createdAt: z.date(), + version: z.number(), + }).optional(), + + // Defaults + limit: z.number().default(10), +}) +``` + +### Schema Descriptions for AI + +When using `generateObject()` from the AI SDK, add `.describe()` to help the LLM understand each field: + +```typescript +z.object({ + title: z.string().describe('Event title, concise, without names'), + startTime: z.string().describe('Start time in HH:MM format (e.g., 14:00)'), + priority: z.enum(['low', 'medium', 'high']).describe('Urgency level'), +}) +``` + +Call `.describe()` at the end of the chain: schema methods like `.min()` return new instances that don't inherit metadata. + +## Handler Context + +The handler context (`ctx`) provides access to Agentuity services: + +```typescript +handler: async (ctx, input) => { + // Logging (Remember: always use ctx.logger, not console.log) + ctx.logger.info('Processing', { data: input }); + ctx.logger.error('Something failed', { error }); + + // Identifiers + ctx.sessionId; // Unique per request (sess_...) + ctx.thread.id; // Conversation context (thrd_...) + ctx.agentName; // Current agent name + + // Call other agents + const result = await ctx.agent.otherAgent.run({ query: input.query }); + + // State management + ctx.state.set('key', value); // Request-scoped (cleared after response) + ctx.thread.state.set('key', value); // Thread-scoped (up to 1 hour) + ctx.session.state.set('key', value); // Session-scoped + + // Storage + await ctx.kv.set('bucket', 'key', data); + await ctx.vector.search('namespace', { query: 'text' }); + await ctx.objectstore.put('bucket', 'file.txt', buffer); + + // Background tasks + ctx.waitUntil(async () => { + await ctx.kv.set('analytics', 'event', { timestamp: Date.now() }); + }); + + return { result }; +} +``` + +For detailed state management patterns, see [Managing State](/Building/Agents/state-management). + +## Agent Metadata + +Every agent requires metadata with a name and description: + +```typescript +const agent = createAgent({ + metadata: { + name: 'Email Processor', + description: 'Processes incoming emails and extracts key information', + }, + schema: { ... }, + handler: async (ctx, input) => { ... }, +}); +``` + +### Workbench Welcome Message + +The Workbench is Agentuity's development UI for testing agents locally and in production. Export a `welcome` function to customize the experience: + +```typescript +export const welcome = () => ({ + welcome: `Welcome to the **Email Processor** agent. + +This agent extracts key information from emails including: +- Sender and recipient +- Subject analysis +- Action items`, + prompts: [ + { + data: JSON.stringify({ email: 'test@example.com', subject: 'Meeting tomorrow' }), + contentType: 'application/json', + }, + { + data: 'Process this email', + contentType: 'text/plain', + }, + ], +}); + +export default agent; +``` + +The `prompts` array provides quick-test options in the Workbench UI. + +## Best Practices + +- **Single responsibility**: Each agent should have one clear purpose +- **Always define schemas**: Schemas provide type safety and serve as documentation +- **Handle errors gracefully**: Wrap external calls in try-catch blocks +- **Keep handlers focused**: Move complex logic to helper functions + +```typescript +// Good: Clear, focused handler +handler: async (ctx, input) => { + try { + const enriched = await enrichData(input.data); + const result = await ctx.agent.processor.run(enriched); + return { success: true, result }; + } catch (error) { + ctx.logger.error('Processing failed', { error }); + return { success: false, error: 'Processing failed' }; + } +} +``` + +## Next Steps + +- [Using the AI SDK](/Building/Agents/ai-sdk-integration): Add LLM capabilities with generateText and streamText +- [Managing State](/Building/Agents/state-management): Persist data across requests with thread and session state +- [Calling Other Agents](/Building/Agents/calling-other-agents): Build multi-agent workflows diff --git a/content/v1/Building/Agents/evaluations.mdx b/content/v1/Building/Agents/evaluations.mdx new file mode 100644 index 00000000..dd8756ce --- /dev/null +++ b/content/v1/Building/Agents/evaluations.mdx @@ -0,0 +1,308 @@ +--- +title: Evaluations +description: Automatically test and validate agent outputs for quality and compliance +--- + +# Evaluations + +Evaluations (evals) are automated tests that run after your agent completes. They validate output quality, check compliance, and monitor performance without blocking agent responses. + +Evals come in two types: **binary** (pass/fail) for yes/no criteria, and **score** (0-1) for quality gradients. + +## Basic Example + +Attach an eval to any agent using `createEval()`: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ question: z.string() }), + output: z.object({ answer: z.string(), confidence: z.number() }), + }, + handler: async (ctx, input) => { + const answer = await generateAnswer(input.question); + return { answer, confidence: 0.95 }; + }, +}); + +// Score eval: returns 0-1 quality score +agent.createEval({ + metadata: { + name: 'confidence-check', + description: 'Scores output based on confidence level', + }, + handler: async (ctx, input, output) => { + return { + success: true, + score: output.confidence, + metadata: { threshold: 0.8 }, + }; + }, +}); + +export default agent; +``` + +Evals run asynchronously after the response is sent, so they don't delay users. + +## Binary vs Score Evals + +### Binary (Pass/Fail) + +Use for yes/no criteria. You can use programmatic checks or LLM-based judgment: + +```typescript +import { generateObject } from 'ai'; +import { openai } from '@ai-sdk/openai'; +import { z } from 'zod'; + +// Programmatic: pattern matching +agent.createEval({ + metadata: { name: 'pii-check' }, + handler: async (ctx, input, output) => { + const ssnPattern = /\b\d{3}-\d{2}-\d{4}\b/; + const hasPII = ssnPattern.test(output.answer); + + return { + success: true, + passed: !hasPII, + metadata: { reason: hasPII ? 'Contains SSN pattern' : 'No PII detected' }, + }; + }, +}); + +// LLM-based: subjective judgment +agent.createEval({ + metadata: { name: 'is-helpful' }, + handler: async (ctx, input, output) => { + const { object } = await generateObject({ + model: openai('gpt-5-nano'), + schema: z.object({ + isHelpful: z.boolean().describe('Whether the response is helpful'), + reason: z.string().describe('Brief explanation'), + }), + prompt: `Evaluate if this response is helpful for the user's question. + +Question: ${input.question} +Response: ${output.answer} + +Consider: Does it answer the question? Is it actionable?`, + }); + + return { + success: true, + passed: object.isHelpful, + metadata: { reason: object.reason }, + }; + }, +}); +``` + +### Score (0-1) + +Use for quality gradients where you need nuance beyond pass/fail: + +```typescript +import { generateObject } from 'ai'; +import { openai } from '@ai-sdk/openai'; +import { z } from 'zod'; + +agent.createEval({ + metadata: { + name: 'relevance-score', + description: 'Scores how relevant the answer is to the question', + }, + handler: async (ctx, input, output) => { + const { object } = await generateObject({ + model: openai('gpt-5-nano'), + schema: z.object({ + score: z.number().min(0).max(1).describe('Relevance score'), + reason: z.string().describe('Brief explanation'), + }), + prompt: `Score how relevant this answer is to the question (0-1). + +Question: ${input.question} +Answer: ${output.answer} + +0 = completely off-topic, 1 = directly addresses the question.`, + }); + + return { + success: true, + score: object.score, + metadata: { reason: object.reason }, + }; + }, +}); +``` + +## LLM-as-Judge Pattern + +The LLM-as-judge pattern uses one model to evaluate another model's output. This is useful for subjective quality assessments that can't be checked programmatically. In this example, a small model judges whether a RAG agent's answer is grounded in the retrieved sources: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { generateObject } from 'ai'; +import { openai } from '@ai-sdk/openai'; +import { z } from 'zod'; + +const ragAgent = createAgent({ + schema: { + input: z.object({ question: z.string() }), + output: z.object({ answer: z.string(), sources: z.array(z.string()) }), + }, + handler: async (ctx, input) => { + const results = await ctx.vector.search('knowledge-base', { + query: input.question, + limit: 3, + }); + + // Store sources for eval access + ctx.state.set('retrievedDocs', results.map((r) => r.metadata?.text || '')); + + const answer = await generateAnswer(input.question, results); + return { answer, sources: results.map((r) => r.id) }; + }, +}); + +ragAgent.createEval({ + metadata: { + name: 'hallucination-check', + description: 'Detects claims not supported by sources', + }, + handler: async (ctx, input, output) => { + const retrievedDocs = ctx.state.get('retrievedDocs') as string[]; + + const { object } = await generateObject({ + model: openai('gpt-5-nano'), + schema: z.object({ + isGrounded: z.boolean(), + unsupportedClaims: z.array(z.string()), + score: z.number().min(0).max(1), + }), + prompt: `Check if this answer is supported by the source documents. + +Question: ${input.question} +Answer: ${output.answer} + +Sources: +${retrievedDocs.join('\n\n')} + +Identify claims NOT supported by the sources.`, + }); + + return { + success: true, + score: object.score, + metadata: { + isGrounded: object.isGrounded, + unsupportedClaims: object.unsupportedClaims, + }, + }; + }, +}); + +export default ragAgent; +``` + + +Data stored in `ctx.state` during agent execution persists to eval handlers. Use this to pass retrieved documents, intermediate results, or timing data. + + + +Built-in evals (`agent.createEval()`) are designed for automated background monitoring of a single agent's output. For user-facing comparison reports or cross-model evaluations (like comparing GPT vs Claude outputs), use custom agent chaining with `generateObject` instead. See [Learning/Examples](/Learning/Examples) for advanced patterns. + + +## Multiple Evals + +Attach multiple evals to assess different quality dimensions. All run in parallel after the agent completes: + +```typescript +import { generateObject } from 'ai'; +import { openai } from '@ai-sdk/openai'; +import { z } from 'zod'; + +// Eval 1: Is the response relevant? +agent.createEval({ + metadata: { name: 'relevance' }, + handler: async (ctx, input, output) => { + const { object } = await generateObject({ + model: openai('gpt-5-nano'), + schema: z.object({ + score: z.number().min(0).max(1), + reason: z.string(), + }), + prompt: `Score relevance (0-1): Does "${output.answer}" answer "${input.question}"?`, + }); + return { success: true, score: object.score, metadata: { reason: object.reason } }; + }, +}); + +// Eval 2: Is it concise? +agent.createEval({ + metadata: { name: 'conciseness' }, + handler: async (ctx, input, output) => { + const { object } = await generateObject({ + model: openai('gpt-5-nano'), + schema: z.object({ + score: z.number().min(0).max(1), + reason: z.string(), + }), + prompt: `Score conciseness (0-1): Is "${output.answer}" clear without unnecessary fluff?`, + }); + return { success: true, score: object.score, metadata: { reason: object.reason } }; + }, +}); + +// Eval 3: Compliance check (programmatic) +agent.createEval({ + metadata: { name: 'no-pii' }, + handler: async (ctx, input, output) => { + const patterns = [/\b\d{3}-\d{2}-\d{4}\b/, /\b\d{16}\b/]; // SSN, credit card + const hasPII = patterns.some((p) => p.test(output.answer)); + return { success: true, passed: !hasPII }; + }, +}); +``` + +Errors in one eval don't affect others. Each runs independently. + +## Error Handling + +Return `success: false` when an eval can't complete: + +```typescript +agent.createEval({ + metadata: { name: 'external-validation' }, + handler: async (ctx, input, output) => { + try { + const response = await fetch('https://api.example.com/validate', { + method: 'POST', + body: JSON.stringify({ text: output.answer }), + signal: AbortSignal.timeout(3000), + }); + + if (!response.ok) { + return { success: false, error: `Service error: ${response.status}` }; + } + + const result = await response.json(); + return { success: true, passed: result.isValid }; + } catch (error) { + ctx.logger.error('Validation failed', { error }); + return { success: false, error: error.message }; + } + }, +}); +``` + +Eval errors are logged but don't affect agent responses. + +## Next Steps + +- [Events & Lifecycle](/Building/Agents/events-lifecycle): Monitor agent execution with lifecycle hooks +- [State Management](/Building/Agents/state-management): Share data between handlers and evals +- [Calling Other Agents](/Building/Agents/calling-other-agents): Build multi-agent workflows diff --git a/content/v1/Building/Agents/events-lifecycle.mdx b/content/v1/Building/Agents/events-lifecycle.mdx new file mode 100644 index 00000000..f26c0f50 --- /dev/null +++ b/content/v1/Building/Agents/events-lifecycle.mdx @@ -0,0 +1,161 @@ +--- +title: Events & Lifecycle +description: Lifecycle hooks for monitoring and extending agent behavior +--- + +# Events & Lifecycle + +Events provide lifecycle hooks for monitoring agent execution. Use them for logging, metrics, analytics, and error tracking. + +## Agent Events + +Track individual agent execution with `started`, `completed`, and `errored` events: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + 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 }); + return { result: `Completed: ${input.task}` }; + }, +}); + +// Track execution timing +agent.addEventListener('started', (event, agent, ctx) => { + ctx.state.set('startTime', Date.now()); + ctx.logger.info('Agent started', { agent: agent.metadata.name }); +}); + +agent.addEventListener('completed', (event, agent, ctx) => { + const startTime = ctx.state.get('startTime') as number; + const duration = Date.now() - startTime; + + ctx.logger.info('Agent completed', { + agent: agent.metadata.name, + durationMs: duration, + }); + + // Warn on slow executions + if (duration > 1000) { + ctx.logger.warn('Slow execution detected', { duration, threshold: 1000 }); + } +}); + +agent.addEventListener('errored', (event, agent, ctx, error) => { + const startTime = ctx.state.get('startTime') as number; + const duration = Date.now() - startTime; + + ctx.logger.error('Agent failed', { + agent: agent.metadata.name, + error: error.message, + durationMs: duration, + }); +}); + +export default agent; +``` + +Event listeners receive: event name, agent instance, context, and (for `errored`) the error object. + +## App-Level Events + +Monitor all agents globally by registering listeners in `app.ts`: + +```typescript +import { createApp } from '@agentuity/runtime'; + +const app = createApp(); + +// Track all agent executions +app.addEventListener('agent.started', (event, agent, ctx) => { + ctx.logger.info('Agent execution started', { + agent: agent.metadata.name, + sessionId: ctx.sessionId, + }); +}); + +app.addEventListener('agent.completed', (event, agent, ctx) => { + ctx.logger.info('Agent execution completed', { + agent: agent.metadata.name, + sessionId: ctx.sessionId, + }); +}); + +app.addEventListener('agent.errored', (event, agent, ctx, error) => { + ctx.logger.error('Agent execution failed', { + agent: agent.metadata.name, + error: error.message, + sessionId: ctx.sessionId, + }); +}); + +export default app.server; +``` + +### Available App Events + +| Event | Description | +|-------|-------------| +| `agent.started` | Any agent starts execution | +| `agent.completed` | Any agent completes successfully | +| `agent.errored` | Any agent throws an error | +| `session.started` | New session begins | +| `session.completed` | Session ends | +| `thread.created` | New thread created | +| `thread.destroyed` | Thread expired or destroyed | + +## Shared State + +Event handlers share state via `ctx.state`: + +```typescript +agent.addEventListener('started', (event, agent, ctx) => { + ctx.state.set('startTime', Date.now()); + ctx.state.set('metadata', { userId: '123', source: 'api' }); +}); + +agent.addEventListener('completed', (event, agent, ctx) => { + const startTime = ctx.state.get('startTime') as number; + const metadata = ctx.state.get('metadata') as Record; + + ctx.logger.info('Execution complete', { + duration: Date.now() - startTime, + ...metadata, + }); +}); +``` + + +Use `ctx.waitUntil()` in event handlers for non-blocking operations like sending metrics to external services: + +```typescript +agent.addEventListener('completed', (event, agent, ctx) => { + ctx.waitUntil(async () => { + await sendMetricsToExternalService({ agent: agent.metadata.name }); + }); +}); +``` + + +## Events vs Evals + +| Aspect | Events | Evals | +|--------|--------|-------| +| **Purpose** | Monitoring, logging | Quality assessment | +| **Timing** | During execution | After completion | +| **Blocking** | Synchronous | Background (`waitUntil`) | +| **Output** | Logs, metrics | Pass/fail, scores | + +Use events for observability. Use [evaluations](/Building/Agents/evaluations) for output quality checks. + +## Next Steps + +- [Evaluations](/Building/Agents/evaluations): Automated quality testing for agent outputs +- [State Management](/Building/Agents/state-management): Thread and session state patterns +- [Calling Other Agents](/Building/Agents/calling-other-agents): Multi-agent coordination diff --git a/content/v1/Building/Agents/meta.json b/content/v1/Building/Agents/meta.json new file mode 100644 index 00000000..58b3529b --- /dev/null +++ b/content/v1/Building/Agents/meta.json @@ -0,0 +1,15 @@ +{ + "title": "Agents", + "pages": [ + "creating-agents", + "ai-sdk-integration", + "ai-gateway", + "streaming-responses", + "state-management", + "schema-libraries", + "calling-other-agents", + "subagents", + "evaluations", + "events-lifecycle" + ] +} diff --git a/content/v1/Building/Agents/schema-libraries.mdx b/content/v1/Building/Agents/schema-libraries.mdx new file mode 100644 index 00000000..97cca4d6 --- /dev/null +++ b/content/v1/Building/Agents/schema-libraries.mdx @@ -0,0 +1,202 @@ +--- +title: Schema Libraries +description: Using Valibot and ArkType as alternatives to Zod +--- + +# Schema Libraries + +Agentuity uses the [StandardSchema](https://github.com/standard-schema/standard-schema) interface for validation, which means you can use any compatible library. While we recommend Zod for most projects, Valibot and ArkType are solid alternatives with different trade-offs. + +## Choosing a Library + +| Library | Bundle Size | Performance | Syntax | Best For | +|---------|-------------|-------------|--------|----------| +| [Zod](https://zod.dev) | ~13.5 kB | Baseline | Method chaining | Most projects (largest ecosystem) | +| [Valibot](https://valibot.dev) | ~1-2 kB | ~2x faster | Modular pipes | Bundle-sensitive apps | +| [ArkType](https://arktype.io) | Lightweight | ~20x faster | TypeScript-like | Performance-critical apps | + +## Using Valibot + +### Installation + +```bash +bun add valibot +``` + +### Basic Schema + +```typescript +import { createAgent, type AgentContext } from '@agentuity/runtime'; +import * as v from 'valibot'; + +const InputSchema = v.object({ + email: v.pipe(v.string(), v.email()), + age: v.pipe(v.number(), v.minValue(0), v.maxValue(120)), + role: v.picklist(['admin', 'user', 'guest']), +}); + +const OutputSchema = v.object({ + success: v.boolean(), + userId: v.string(), +}); + +const agent = createAgent({ + schema: { + input: InputSchema, + output: OutputSchema, + }, + handler: async (ctx: AgentContext, input) => { + ctx.logger.info('Processing user', { email: input.email, role: input.role }); + return { + success: true, + userId: crypto.randomUUID(), + }; + }, +}); + +export default agent; +``` + +### Common Patterns + +```typescript +import * as v from 'valibot'; + +// Strings +v.string() // Any string +v.pipe(v.string(), v.minLength(5)) // Minimum length +v.pipe(v.string(), v.maxLength(100)) // Maximum length +v.pipe(v.string(), v.email()) // Email format +v.pipe(v.string(), v.url()) // URL format + +// Numbers +v.number() // Any number +v.pipe(v.number(), v.minValue(0)) // Minimum value +v.pipe(v.number(), v.maxValue(100)) // Maximum value +v.pipe(v.number(), v.integer()) // Integer only + +// Arrays and objects +v.array(v.string()) // Array of strings +v.object({ name: v.string() }) // Object shape + +// Optional and nullable +v.optional(v.string()) // string | undefined +v.nullable(v.string()) // string | null +v.nullish(v.string()) // string | null | undefined + +// Enums +v.picklist(['a', 'b', 'c']) // One of these values +v.literal('exact') // Exact value + +// Defaults +v.optional(v.string(), 'default') // With default value +``` + +## Using ArkType + +### Installation + +```bash +bun add arktype +``` + +### Basic Schema + +```typescript +import { createAgent, type AgentContext } from '@agentuity/runtime'; +import { type } from 'arktype'; + +const InputSchema = type({ + email: 'string.email', + age: 'number>=0&<=120', + role: '"admin"|"user"|"guest"', +}); + +const OutputSchema = type({ + success: 'boolean', + userId: 'string', +}); + +const agent = createAgent({ + schema: { + input: InputSchema, + output: OutputSchema, + }, + handler: async (ctx: AgentContext, input) => { + ctx.logger.info('Processing user', { email: input.email, role: input.role }); + return { + success: true, + userId: crypto.randomUUID(), + }; + }, +}); + +export default agent; +``` + +### Common Patterns + +```typescript +import { type } from 'arktype'; + +// Strings +type('string') // Any string +type('string>5') // Minimum length (greater than 5 chars) +type('string<100') // Maximum length +type('string.email') // Email format +type('string.url') // URL format + +// Numbers +type('number') // Any number +type('number>0') // Greater than 0 +type('number<=100') // Less than or equal to 100 +type('integer') // Integer only +type('number>=0&<=120') // Range (0 to 120) + +// Arrays and objects +type('string[]') // Array of strings +type({ name: 'string' }) // Object shape + +// Optional and union +type('string?') // string | undefined +type('string|null') // string | null + +// Enums and literals +type('"a"|"b"|"c"') // One of these values +type('"exact"') // Exact value + +// Nested objects +type({ + user: { + name: 'string', + email: 'string.email', + }, + tags: 'string[]?', +}) +``` + +## Migrating Between Libraries + +Schemas are interchangeable in `createAgent()`. The same agent structure works with any StandardSchema library: + +```typescript +// With Zod +import { z } from 'zod'; +const schema = { input: z.object({ name: z.string() }) }; + +// With Valibot +import * as v from 'valibot'; +const schema = { input: v.object({ name: v.string() }) }; + +// With ArkType +import { type } from 'arktype'; +const schema = { input: type({ name: 'string' }) }; + +// All work the same way in createAgent() +createAgent({ schema, handler: async (ctx, input) => { ... } }); +``` + +## Next Steps + +- [Creating Agents](/Building/Agents/creating-agents): Full guide to agent creation with Zod examples +- [Using the AI SDK](/Building/Agents/ai-sdk-integration): Add LLM capabilities to your agents diff --git a/content/v1/Building/Agents/state-management.mdx b/content/v1/Building/Agents/state-management.mdx new file mode 100644 index 00000000..6e7097e3 --- /dev/null +++ b/content/v1/Building/Agents/state-management.mdx @@ -0,0 +1,355 @@ +--- +title: Managing State +description: Request, thread, and session state for stateful agents +--- + +# Managing State + +Agentuity provides three state scopes for managing data across requests: + +- request state for temporary calculations +- thread state for conversation context +- session state for user-level data + +## The Three State Scopes + +| Scope | Lifetime | Cleared When | Access | Example Use Case | +|-------|----------|--------------|--------|----------| +| Request | Single request | After response sent | `ctx.state` | Timing, temp calculations | +| Thread | Up to 1 hour | Expiration or `destroy()` | `ctx.thread.state` | Conversation history | +| Session | Spans threads | In-memory (server restart) | `ctx.session.state` | User preferences | + +### Quick Example + +```typescript +import { createAgent, type AgentContext } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ message: z.string() }), + output: z.object({ + response: z.string(), + requestTime: z.number(), + messageCount: z.number(), + }), + }, + handler: async (ctx: AgentContext, input) => { + // REQUEST STATE: Cleared after this response + ctx.state.set('startTime', Date.now()); + + // THREAD STATE: Persists across requests (up to 1 hour) + const messages = (ctx.thread.state.get('messages') as string[]) || []; + messages.push(input.message); + ctx.thread.state.set('messages', messages); + + // SESSION STATE: Persists across threads + const totalRequests = (ctx.session.state.get('totalRequests') as number) || 0; + ctx.session.state.set('totalRequests', totalRequests + 1); + + const requestTime = Date.now() - (ctx.state.get('startTime') as number); + + return { + response: `Received: ${input.message}`, + requestTime, + messageCount: messages.length, + }; + }, +}); + +export default agent; +``` + +## Request State + +Request state (`ctx.state`) holds temporary data within a single request. It's cleared automatically after the response is sent. + +```typescript +handler: async (ctx, input) => { + // Track timing + ctx.state.set('startTime', Date.now()); + + // Process request... + const result = await processData(input); + + // Use the timing data + const duration = Date.now() - (ctx.state.get('startTime') as number); + ctx.logger.info('Request completed', { durationMs: duration }); + + return result; +} +``` + +**Use cases:** Request timing, temporary calculations, passing data between event listeners. + +## Thread State + +Thread state (`ctx.thread.state`) persists across multiple requests within a conversation, expiring after 1 hour of inactivity. Thread identity is managed automatically via cookies. + +### Conversation Memory + +```typescript +import { createAgent, type AgentContext } from '@agentuity/runtime'; +import { generateText } from 'ai'; +import { openai } from '@ai-sdk/openai'; +import { z } from 'zod'; + +interface Message { + role: 'user' | 'assistant'; + content: string; +} + +const agent = createAgent({ + schema: { + input: z.object({ message: z.string() }), + output: z.string(), + }, + handler: async (ctx: AgentContext, input) => { + // Initialize on first request + if (!ctx.thread.state.has('messages')) { + ctx.thread.state.set('messages', []); + ctx.thread.state.set('turnCount', 0); + } + + const messages = ctx.thread.state.get('messages') as Message[]; + const turnCount = ctx.thread.state.get('turnCount') as number; + + // Add user message + messages.push({ role: 'user', content: input.message }); + + // Generate response with conversation context + const { text } = await generateText({ + model: openai('gpt-5-mini'), + system: 'You are a helpful assistant. Reference previous messages when relevant.', + messages, + }); + + // Update thread state + messages.push({ role: 'assistant', content: text }); + ctx.thread.state.set('messages', messages); + ctx.thread.state.set('turnCount', turnCount + 1); + + ctx.logger.info('Conversation turn', { + threadId: ctx.thread.id, + turnCount: turnCount + 1, + }); + + return text; + }, +}); + +export default agent; +``` + +### Thread Properties and Methods + +```typescript +ctx.thread.id; // Thread ID (thrd_...) +ctx.thread.state.set('key', value); +ctx.thread.state.get('key'); +ctx.thread.state.has('key'); +ctx.thread.state.delete('key'); + +// Reset the conversation +await ctx.thread.destroy(); +``` + +### Resetting a Conversation + +Call `ctx.thread.destroy()` to clear all thread state and start fresh: + +```typescript +handler: async (ctx, input) => { + if (input.command === 'reset') { + await ctx.thread.destroy(); + return 'Conversation reset. Thread state cleared.'; + } + + // Continue conversation... +} +``` + +## Session State + +Session state (`ctx.session.state`) spans multiple threads, useful for user-level data that should persist across conversations. + +```typescript +handler: async (ctx, input) => { + // Track user activity across threads + const visits = (ctx.session.state.get('visits') as number) || 0; + ctx.session.state.set('visits', visits + 1); + ctx.session.state.set('lastActive', new Date().toISOString()); + + ctx.logger.info('Session activity', { + sessionId: ctx.sessionId, + visits: visits + 1, + }); + + return { visits: visits + 1 }; +} +``` + +**Note:** Session state is in-memory and cleared on server restart. For durable data, use [KV storage](/Building/Storage/key-value). + +## Persisting to Storage + +In-memory state is lost on server restart. For durability, combine state management with KV storage: + +### Load → Cache → Save Pattern + +```typescript +import { createAgent, type AgentContext } from '@agentuity/runtime'; +import { streamText } from 'ai'; +import { openai } from '@ai-sdk/openai'; +import { z } from 'zod'; + +type Message = { role: 'user' | 'assistant'; content: string }; + +const agent = createAgent({ + schema: { + input: z.object({ message: z.string() }), + stream: true, + }, + handler: async (ctx: AgentContext, input) => { + const key = `chat_${ctx.thread.id}`; + let messages: Message[] = []; + + // Load from KV on first access in this thread + if (!ctx.thread.state.has('loaded')) { + const result = await ctx.kv.get('conversations', key); + if (result.exists) { + messages = result.data; + ctx.logger.info('Loaded conversation from KV', { messageCount: messages.length }); + } + ctx.thread.state.set('messages', messages); + ctx.thread.state.set('loaded', true); + } else { + messages = ctx.thread.state.get('messages') as Message[]; + } + + // Add user message + messages.push({ role: 'user', content: input.message }); + + // Stream response + const result = streamText({ + model: openai('gpt-5-mini'), + messages, + }); + + // Save in background (non-blocking) + ctx.waitUntil(async () => { + const fullText = await result.text; + messages.push({ role: 'assistant', content: fullText }); + + // Keep last 20 messages to bound state size + const recentMessages = messages.slice(-20); + ctx.thread.state.set('messages', recentMessages); + + // Persist to KV + await ctx.kv.set('conversations', key, recentMessages, { + ttl: 86400, // 24 hours + }); + }); + + return result.textStream; + }, +}); + +export default agent; +``` + +**Key points:** +- Load from KV once per thread, cache in thread state +- Use `ctx.waitUntil()` for non-blocking saves +- Bound state size to prevent unbounded growth + +## Thread Lifecycle + +Threads expire after 1 hour of inactivity. Use the `destroyed` event to run cleanup logic: + +```typescript +handler: async (ctx, input) => { + // Register cleanup handler once per thread + if (!ctx.thread.state.has('cleanupRegistered')) { + ctx.thread.addEventListener('destroyed', async (eventName, thread) => { + const messages = thread.state.get('messages') as string[] || []; + + if (messages.length > 0) { + // Save conversation before thread expires + await ctx.kv.set('archives', thread.id, { + messages, + endedAt: new Date().toISOString(), + }, { ttl: 604800 }); // 7 days + + ctx.logger.info('Archived conversation', { + threadId: thread.id, + messageCount: messages.length, + }); + } + }); + + ctx.thread.state.set('cleanupRegistered', true); + } + + // Process request... +} +``` + +### Session Completion Events + +Track when sessions (individual requests) complete: + +```typescript +ctx.session.addEventListener('completed', async (eventName, session) => { + ctx.logger.info('Session completed', { + sessionId: session.id, + threadId: session.thread.id, + }); +}); +``` + +For app-level event monitoring, see [Events & Lifecycle](/Building/Agents/events-lifecycle). + +## Thread vs Session IDs + +| ID | Format | Lifetime | Purpose | +|----|--------|----------|---------| +| Thread ID | `thrd_` | Up to 1 hour (shared across requests) | Group related requests into conversations | +| Session ID | `sess_` | Single request (unique per call) | Request tracing and analytics | + +```typescript +handler: async (ctx, input) => { + ctx.logger.info('Request received', { + threadId: ctx.thread.id, // Same across conversation + sessionId: ctx.sessionId, // Unique per request + }); + + return { threadId: ctx.thread.id, sessionId: ctx.sessionId }; +} +``` + +## Best Practices + +- **Use the right scope**: Request for temp data, thread for conversations, session for user data +- **Keep state bounded**: Limit conversation history (e.g., last 20-50 messages) +- **Persist important data**: Don't rely on state for data that must survive restarts +- **Clean up resources**: Use `destroyed` event to save or archive data +- **Cache strategically**: Load from KV once, cache in state, save on completion + +```typescript +// Good: Bounded conversation history +const messages = ctx.thread.state.get('messages') as Message[]; +if (messages.length > 50) { + const archived = messages.slice(0, -50); + ctx.waitUntil(async () => { + await ctx.kv.set('archives', `${ctx.thread.id}_${Date.now()}`, archived); + }); + ctx.thread.state.set('messages', messages.slice(-50)); +} +``` + +## Next Steps + +- [Key-Value Storage](/Building/Storage/key-value): Durable data persistence with namespaces and TTL +- [Calling Other Agents](/Building/Agents/calling-other-agents): Share state between agents in workflows +- [Events & Lifecycle](/Building/Agents/events-lifecycle): Monitor agent execution and cleanup diff --git a/content/v1/Building/Agents/streaming-responses.mdx b/content/v1/Building/Agents/streaming-responses.mdx new file mode 100644 index 00000000..8d862692 --- /dev/null +++ b/content/v1/Building/Agents/streaming-responses.mdx @@ -0,0 +1,226 @@ +--- +title: Returning Streaming Responses +description: Return real-time LLM output with streaming agents +--- + +# Returning Streaming Responses + +Show LLM output as it's generated instead of waiting for the full response. Streaming reduces perceived latency and creates a more responsive experience. + +## Streaming Types + +Agentuity supports two streaming patterns: + +### Ephemeral Streaming + +Uses `router.stream()` for direct streaming to the HTTP client. Data flows through and is not stored. Use this for real-time chat responses. + +```typescript +// In route.ts +router.stream('/', async (c) => { + return await c.agent.chat.run({ message: '...' }); +}); +``` + +### Persistent Streaming + +Uses `ctx.stream.create()` to create stored streams with public URLs. Data persists and can be accessed after the connection closes. Use this for batch processing, exports, or content that needs to be accessed later. + +```typescript +// In agent.ts +const stream = await ctx.stream.create('my-export', { + contentType: 'text/csv', +}); +await stream.write('data'); +await stream.close(); +``` + +This page focuses on ephemeral streaming with the AI SDK. For persistent streaming patterns, see the [Storage documentation](/Building/Storage/durable-streams). + + +Streaming requires both: `schema.stream: true` in your agent (so the handler returns a stream) and `router.stream()` in your route (so the response is streamed to the client). + + +## Basic Streaming + +Enable streaming by setting `stream: true` in your schema and returning a `textStream`: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { streamText } from 'ai'; +import { anthropic } from '@ai-sdk/anthropic'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ message: z.string() }), + stream: true, + }, + handler: async (ctx, input) => { + const { textStream } = streamText({ + model: anthropic('claude-sonnet-4-5'), + prompt: input.message, + }); + + return textStream; + }, +}); + +export default agent; +``` + +## Route Configuration + +Use `router.stream()` to handle streaming responses: + +```typescript +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +router.stream('/', async (c) => { + const body = await c.req.json(); + return c.agent.chat.run(body); +}); + +export default router; +``` + + +Use `router.stream()` for streaming agents. Regular `router.post()` works but may buffer the response depending on the client. + + +## Consuming Streams + +### With Fetch API + +Read the stream using the Fetch API: + +```typescript +const response = await fetch('http://localhost:3500/chat', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ message: 'Tell me a story' }), +}); + +const reader = response.body?.getReader(); +const decoder = new TextDecoder(); + +while (reader) { + const { done, value } = await reader.read(); + if (done) break; + + const text = decoder.decode(value); + // Process each chunk as it arrives + appendToUI(text); +} +``` + +### With React + +Use the `useAgent` hook from `@agentuity/react`: + +```tsx +import { useAgent } from '@agentuity/react'; + +function Chat() { + const { data, running, run } = useAgent('chat'); + + const handleSubmit = async (message: string) => { + await run({ message }); + }; + + return ( +
+ {running &&

Generating...

} + {data &&

{data}

} + +
+ ); +} +``` + +For streaming with React, see [Frontend Hooks](/Building/Frontend/react-hooks). + +## Streaming with System Prompts + +Add context to streaming responses: + +```typescript +handler: async (ctx, input) => { + const { textStream } = streamText({ + model: anthropic('claude-sonnet-4-5'), + system: 'You are a helpful assistant. Be concise.', + messages: [ + { role: 'user', content: input.message }, + ], + }); + + return textStream; +} +``` + +## Streaming with Conversation History + +Combine streaming with thread state for multi-turn conversations: + +```typescript +handler: async (ctx, input) => { + // Get existing messages from thread state + const messages = ctx.thread.state.get('messages') || []; + + // Add new user message + messages.push({ role: 'user', content: input.message }); + + const { textStream, text } = streamText({ + model: anthropic('claude-sonnet-4-5'), + messages, + }); + + // Save assistant response after streaming completes + ctx.waitUntil(async () => { + const fullText = await text; + messages.push({ role: 'assistant', content: fullText }); + ctx.thread.state.set('messages', messages); + }); + + return textStream; +} +``` + + +Use `ctx.waitUntil()` to save conversation history without blocking the stream. The response starts immediately while state updates happen in the background. + + +## When to Stream + +| Scenario | Recommendation | +|----------|----------------| +| Chat interfaces | Stream for better UX | +| Long-form content | Stream to show progress | +| Quick classifications | Buffer (faster overall, consider Groq for speed) | +| Structured data | Buffer (use `generateObject`) | + +## Error Handling + +Handle streaming errors with the `onError` callback: + +```typescript +const { textStream } = streamText({ + model: anthropic('claude-sonnet-4-5'), + prompt: input.message, + onError: (error) => { + ctx.logger.error('Stream error', { error }); + }, +}); +``` + + +Errors in streaming are part of the stream, not thrown exceptions. Always provide an `onError` callback. + + +## Next Steps + +- [Using the AI SDK](/Building/Agents/ai-sdk-integration): Structured output and non-streaming responses +- [State Management](/Building/Agents/state-management): Multi-turn conversations with memory +- [Server-Sent Events](/Building/Routes-Triggers/sse): Server-push updates without polling diff --git a/content/v1/Building/Agents/subagents.mdx b/content/v1/Building/Agents/subagents.mdx new file mode 100644 index 00000000..8371a6e9 --- /dev/null +++ b/content/v1/Building/Agents/subagents.mdx @@ -0,0 +1,425 @@ +--- +title: Subagents +description: Organize related agents into parent-child hierarchies +--- + +# Subagents + +Group related agents under a parent to create hierarchical organizations. Subagents share context with their parent, inherit route paths, and can access their parent via `ctx.parent`. + +## When to Use Subagents + +| Scenario | Use Subagents | Use Separate Agents | +|----------|---------------|---------------------| +| Related domain operations | Team members, tasks | User auth, payments | +| Shared validation needed | Parent validates once | Each validates independently | +| Hierarchical API structure | `/team/members`, `/team/tasks` | `/users`, `/orders` | +| Different team ownership | Same team | Different teams | +| Testing isolation | Moderate coupling | Full independence | + +**Use subagents when:** +- Operations belong to the same domain (e.g., team/members, team/tasks) +- Parent needs to provide shared validation or configuration +- You want hierarchical route paths + +**Use separate agents when:** +- Domains are independent +- Agents may be called from many different contexts +- Teams maintain different parts of the system + +## File Structure + +Create subagents by placing agent files in subdirectories of the parent: + +``` +src/agents/ +└── team/ # Parent agent + ├── agent.ts # Parent logic + ├── route.ts # Parent routes (/team) + ├── members/ # Subagent + │ ├── agent.ts # Subagent logic + │ └── route.ts # Subagent routes (/team/members) + └── tasks/ # Subagent + ├── agent.ts + └── route.ts # Routes at /team/tasks +``` + +**Naming convention:** +- Parent agent: `team` +- Subagent names: `team.members`, `team.tasks` +- Access via: `ctx.agent.team.members.run()` + + +Only one level of nesting is supported. Grandchildren (`team/members/admins/`) are not allowed. If you need deeper organization, create additional subagents at the same level. + + +## Creating a Parent Agent + +Parent agents coordinate subagents and provide shared logic: + +```typescript +// team/agent.ts +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + metadata: { + name: 'Team Agent', + description: 'Coordinates team operations', + }, + schema: { + input: z.object({ + action: z.enum(['info', 'stats']), + }), + output: z.object({ + message: z.string(), + memberCount: z.number().optional(), + taskCount: z.number().optional(), + }), + }, + handler: async (ctx, input) => { + if (input.action === 'info') { + return { message: 'Team management system' }; + } + + // Coordinate subagents in parallel + const [members, tasks] = await Promise.all([ + ctx.agent.team.members.run({ action: 'count' }), + ctx.agent.team.tasks.run({ action: 'count' }), + ]); + + return { + message: 'Team statistics', + memberCount: members.count, + taskCount: tasks.count, + }; + }, +}); + +export default agent; +``` + +## Creating Subagents + +Subagents are regular agents in subdirectories: + +```typescript +// team/members/agent.ts +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + metadata: { + name: 'Members Subagent', + description: 'Manages team members', + }, + schema: { + input: z.object({ + action: z.enum(['list', 'add', 'remove', 'count']), + name: z.string().optional(), + }), + output: z.object({ + members: z.array(z.string()), + count: z.number().optional(), + }), + }, + handler: async (ctx, input) => { + const stored = await ctx.kv.get('team', 'members'); + let members: string[] = stored.exists ? stored.data : []; + + switch (input.action) { + case 'add': + if (input.name && !members.includes(input.name)) { + members.push(input.name); + await ctx.kv.set('team', 'members', members); + } + break; + + case 'remove': + if (input.name) { + members = members.filter((m) => m !== input.name); + await ctx.kv.set('team', 'members', members); + } + break; + + case 'count': + return { members, count: members.length }; + } + + return { members }; + }, +}); + +export default agent; +``` + +The agent name automatically becomes `team.members` based on the directory structure. + +## Accessing Subagents + +Access subagents using dot notation on `ctx.agent`: + +```typescript +// From any route or agent +const router = createRouter(); + +router.get('/summary', async (c) => { + // Call subagents in parallel + const [members, tasks] = await Promise.all([ + c.agent.team.members.run({ action: 'list' }), + c.agent.team.tasks.run({ action: 'list' }), + ]); + + return c.json({ + memberCount: members.members.length, + taskCount: tasks.tasks.length, + }); +}); +``` + +**Access siblings from a subagent:** + +```typescript +// From team/members/agent.ts +handler: async (ctx, input) => { + // Access sibling via full path (not ctx.sibling) + const tasks = await ctx.agent.team.tasks.run({ action: 'list' }); + return { members: [], relatedTasks: tasks.tasks }; +} +``` + +## Parent Context Access + +Subagents can call their parent via `ctx.parent`: + +```typescript +// team/members/agent.ts +const agent = createAgent({ + handler: async (ctx, input) => { + let parentInfo: string | undefined; + + // ctx.parent is only available in subagents + if (ctx.parent) { + const result = await ctx.parent.run({ action: 'info' }); + parentInfo = result.message; + + ctx.logger.info('Accessed parent', { + parentMessage: parentInfo, + currentAgent: ctx.agentName, // "team.members" + }); + } + + return { members: [], parentInfo }; + }, +}); +``` + + +Always check `if (ctx.parent)` before calling. The property is `undefined` for top-level agents. + + +### Shared Validation Pattern + +Use the parent for centralized validation: + +```typescript +// team/agent.ts - Parent validates permissions +const teamAgent = createAgent({ + schema: { + input: z.object({ + userId: z.string(), + teamId: z.string(), + requiredRole: z.enum(['member', 'admin']).optional(), + }), + output: z.object({ + authorized: z.boolean(), + userRole: z.string(), + }), + }, + handler: async (ctx, input) => { + const membership = await ctx.kv.get( + 'memberships', + `${input.teamId}:${input.userId}` + ); + + if (!membership.exists) { + throw new Error('User is not a member of this team'); + } + + const role = membership.data.role; + + // Check role if required + if (input.requiredRole) { + const hierarchy = { member: 1, admin: 2 }; + if (hierarchy[role] < hierarchy[input.requiredRole]) { + throw new Error(`Requires ${input.requiredRole} role`); + } + } + + return { authorized: true, userRole: role }; + }, +}); + +// team/members/agent.ts - Subagent uses parent validation +const membersAgent = createAgent({ + handler: async (ctx, input) => { + // Validate admin access through parent + if (ctx.parent) { + await ctx.parent.run({ + userId: input.userId, + teamId: input.teamId, + requiredRole: 'admin', + }); + } + + // Admin-only operation + const members = await ctx.kv.get('members', input.teamId); + return { members: members.data || [] }; + }, +}); +``` + +Validation errors propagate to the caller automatically. + +## Route Organization + +Subagent routes inherit the parent path: + +``` +Parent routes: /team, /team/stats +Subagent routes: /team/members, /team/members/add + /team/tasks, /team/tasks/create +``` + +### Subagent Route Example + +```typescript +// team/members/route.ts +import { createRouter } from '@agentuity/runtime'; +import { zValidator } from '@hono/zod-validator'; +import { z } from 'zod'; + +const router = createRouter(); + +// GET /team/members +router.get('/', async (c) => { + const result = await c.agent.team.members.run({ action: 'list' }); + return c.json(result); +}); + +// POST /team/members/add +router.post( + '/add', + zValidator('json', z.object({ name: z.string() })), + async (c) => { + const data = c.req.valid('json'); + const result = await c.agent.team.members.run({ + action: 'add', + name: data.name, + }); + return c.json(result); + } +); + +export default router; +``` + +### Middleware Cascade + +Middleware on parent routes applies to all subagent routes: + +```typescript +// team/route.ts +const router = createRouter(); + +// Auth middleware applies to /team/* including /team/members/* +router.use('/*', async (c, next) => { + const token = c.req.header('Authorization'); + if (!token) { + return c.json({ error: 'Unauthorized' }, 401); + } + await next(); +}); + +router.get('/', async (c) => { + const result = await c.agent.team.run({ action: 'info' }); + return c.json(result); +}); + +export default router; +``` + +## Best Practices + +### Parent Coordinates, Children Execute + +```typescript +// Good: Parent coordinates +const teamAgent = createAgent({ + handler: async (ctx, input) => { + const [members, tasks] = await Promise.all([ + ctx.agent.team.members.run({ action: 'count' }), + ctx.agent.team.tasks.run({ action: 'count' }), + ]); + return { memberCount: members.count, taskCount: tasks.count }; + }, +}); + +// Avoid: Parent implements domain logic directly +const teamAgent = createAgent({ + handler: async (ctx, input) => { + const members = await ctx.kv.get('members', 'list'); + return { count: members.data.length }; // Should delegate to subagent + }, +}); +``` + +### Keep Subagents Independent + +- Minimize dependencies on parent +- Use parent for validation, not core logic +- Each subagent should have a clear, single responsibility + +### Cache Parent Results + +If calling parent multiple times, cache the result: + +```typescript +handler: async (ctx, input) => { + // Call once, use multiple times + const parentData = ctx.parent + ? await ctx.parent.run({ teamId: input.teamId }) + : null; + + // Use parentData throughout handler + return { processed: true }; +} +``` + +### Avoid Circular Calls + +Don't create infinite loops between parent and child: + +```typescript +// Parent calls child +const parent = createAgent({ + handler: async (ctx, input) => { + return await ctx.agent.parent.child.run(input); + }, +}); + +// Child calls parent - creates infinite loop! +const child = createAgent({ + handler: async (ctx, input) => { + if (ctx.parent) { + return await ctx.parent.run(input); // Loops back to parent + } + }, +}); +``` + +## Next Steps + +- [Calling Other Agents](/Building/Agents/calling-other-agents): Communication patterns between agents +- [HTTP Routes](/Building/Routes-Triggers/http-routes): Complete routing patterns and middleware +- [State Management](/Building/Agents/state-management): Share data between parent and subagents diff --git a/content/v1/Building/Routes-Triggers/cron.mdx b/content/v1/Building/Routes-Triggers/cron.mdx new file mode 100644 index 00000000..ac5b46e5 --- /dev/null +++ b/content/v1/Building/Routes-Triggers/cron.mdx @@ -0,0 +1,119 @@ +--- +title: Scheduled Jobs (Cron) +description: Run tasks on a schedule with router.cron() +--- + +Schedule recurring tasks using cron expressions. Schedules are defined in code, deployed with your agents, and automatically provisioned. + +## Basic Example + +```typescript +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +router.cron('0 9 * * *', async (c) => { + c.var.logger.info('Daily report starting'); + + const report = await c.agent.reportGenerator.run({ + type: 'daily', + date: new Date().toISOString(), + }); + + await c.kv.set('reports', `daily-${Date.now()}`, report); + + return c.text('OK'); +}); + +export default router; +``` + +## Cron Syntax + +Standard five-field cron format: + +``` +┌───────────── minute (0-59) +│ ┌───────────── hour (0-23) +│ │ ┌───────────── day of month (1-31) +│ │ │ ┌───────────── month (1-12) +│ │ │ │ ┌───────────── day of week (0-6, Sunday=0) +│ │ │ │ │ +* * * * * +``` + +## Common Schedules + +| Schedule | Expression | Description | +|----------|------------|-------------| +| Every 5 minutes | `*/5 * * * *` | Health checks, quick sync | +| Hourly | `0 * * * *` | Aggregations, cleanup | +| Daily at 9am | `0 9 * * *` | Reports, notifications | +| Weekly (Sunday midnight) | `0 0 * * 0` | Weekly summaries | +| Monthly (1st at midnight) | `0 0 1 * *` | Monthly reports | + +## Full Example + +A health check that monitors external services and stores results: + +```typescript +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +router.cron('*/5 * * * *', async (c) => { + const startTime = Date.now(); + c.var.logger.info('Health check starting'); + + try { + const status = await c.agent.healthChecker.run({ + services: ['api', 'database', 'cache'], + }); + + await c.kv.set('monitoring', 'latest-health', { + ...status, + checkedAt: new Date().toISOString(), + durationMs: Date.now() - startTime, + }); + + c.var.logger.info('Health check completed', { status }); + return c.text('OK'); + } catch (error) { + c.var.logger.error('Health check failed', { error }); + return c.text('ERROR', 500); + } +}); + +export default router; +``` + +## Testing Locally + +Cron jobs only trigger in deployed environments. For local testing, add a manual POST route: + +```typescript +router.cron('0 9 * * *', cronHandler); + +// Manual trigger for local development +router.post('/trigger', async (c) => { + return cronHandler(c); +}); + +async function cronHandler(c) { + const result = await c.agent.dailyReport.run({ date: new Date() }); + return c.json(result); +} +``` + +## Best Practices + +- **Log job start and completion** for debugging scheduled tasks +- **Use `c.kv` or `c.objectstore`** to persist results for later inspection +- **Handle errors gracefully** so one failure doesn't break the schedule +- **Keep jobs idempotent** when possible, in case of retries + +## Next Steps + +- [HTTP Routes](/Building/Routes-Triggers/http-routes) — Standard request/response endpoints +- [Email Handling](/Building/Routes-Triggers/email) — Process incoming emails +- [Key-Value Storage](/Building/Storage/key-value) — Store job results diff --git a/content/v1/Building/Routes-Triggers/email.mdx b/content/v1/Building/Routes-Triggers/email.mdx new file mode 100644 index 00000000..73d0cbeb --- /dev/null +++ b/content/v1/Building/Routes-Triggers/email.mdx @@ -0,0 +1,129 @@ +--- +title: Email Handling +description: Process incoming emails with router.email() +--- + +Handle incoming emails sent to a specific address. Email addresses are defined in code and automatically provisioned when deployed. + +## Basic Example + +```typescript +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +router.email('support@example.com', async (email, c) => { + c.var.logger.info('Email received', { + from: email.fromEmail(), + subject: email.subject(), + }); + + const response = await c.agent.supportAgent.run({ + sender: email.fromEmail() || 'unknown', + subject: email.subject() || 'No Subject', + body: email.text() || '', + }); + + return c.text('Email processed'); +}); + +export default router; +``` + +## Email Object API + +The `email` parameter provides methods to access email content: + +```typescript +// Sender information +email.fromEmail(); // "sender@example.com" or null +email.fromName(); // "John Doe" or null + +// Recipients +email.to(); // All recipients (comma-separated) +email.toEmail(); // First recipient email +email.toName(); // First recipient name + +// Content +email.subject(); // "Re: Your inquiry" or null +email.text(); // Plain text body or null +email.html(); // HTML body or null + +// Attachments +email.attachments(); // Array of { filename, contentType } + +// Metadata +email.date(); // Date object or null +email.messageId(); // Message-ID header or null +email.headers(); // Full Headers object +``` + +## Accessing Attachments + +```typescript +router.email('uploads@example.com', async (email, c) => { + const attachments = email.attachments(); + + for (const attachment of attachments) { + c.var.logger.info('Attachment received', { + filename: attachment.filename, + contentType: attachment.contentType, + }); + } + + return c.text(`Received ${attachments.length} attachments`); +}); +``` + +## Full Example + +A support inbox that categorizes and routes emails: + +```typescript +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +router.email('support@myapp.com', async (email, c) => { + const from = email.fromEmail() || 'unknown'; + const subject = email.subject() || 'No Subject'; + const body = email.text() || email.html() || ''; + const attachmentCount = email.attachments().length; + + try { + // Classify and process the email + const result = await c.agent.supportRouter.run({ + from, + subject, + body, + hasAttachments: attachmentCount > 0, + }); + + // Store for audit trail + await c.kv.set('emails', `${Date.now()}-${from}`, { + from, + subject, + processedAt: new Date().toISOString(), + category: result.category, + }); + + c.var.logger.info('Email processed', { from, category: result.category }); + return c.text('OK'); + } catch (error) { + c.var.logger.error('Email processing failed', { error, from }); + return c.text('Error', 500); + } +}); + +export default router; +``` + + +The `email.sendReply()` method for sending replies is coming soon. Currently, you can process emails and trigger notifications through other channels (Slack, SMS, etc.) or store responses for manual follow-up. + + +## Next Steps + +- [SMS Handling](/Building/Routes-Triggers/sms) — Process incoming text messages +- [Scheduled Jobs](/Building/Routes-Triggers/cron) — Run tasks on a schedule +- [Key-Value Storage](/Building/Storage/key-value) — Store processed email data diff --git a/content/v1/Building/Routes-Triggers/http-routes.mdx b/content/v1/Building/Routes-Triggers/http-routes.mdx new file mode 100644 index 00000000..33f74cad --- /dev/null +++ b/content/v1/Building/Routes-Triggers/http-routes.mdx @@ -0,0 +1,206 @@ +--- +title: HTTP Routes +description: Define GET, POST, and other HTTP endpoints with createRouter() +--- + +Routes define how your application responds to HTTP requests. Built on [Hono](https://hono.dev), the router provides a familiar Express-like API with full TypeScript support. + + +Routes are defined in your codebase (`route.ts` files) and automatically discovered by the SDK. Changes are tracked in git, reviewed in pull requests, and deployed with your code. No UI configuration required. + + +## Basic Routes + +Create routes using `createRouter()`: + +```typescript +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +router.get('/', async (c) => { + return c.json({ status: 'healthy' }); +}); + +router.post('/process', async (c) => { + const body = await c.req.json(); + return c.json({ received: body }); +}); + +export default router; +``` + +## HTTP Methods + +The router supports all standard HTTP methods: + +```typescript +router.get('/items', handler); // Read +router.post('/items', handler); // Create +router.put('/items/:id', handler); // Replace +router.patch('/items/:id', handler); // Update +router.delete('/items/:id', handler); // Delete +``` + +## Route Parameters + +Capture URL segments with `:paramName`: + +```typescript +router.get('/users/:id', async (c) => { + const userId = c.req.param('id'); + return c.json({ userId }); +}); + +router.get('/posts/:year/:month/:slug', async (c) => { + const { year, month, slug } = c.req.param(); + return c.json({ year, month, slug }); +}); +``` + +## Query Parameters + +Access query strings with `c.req.query()`: + +```typescript +router.get('/search', async (c) => { + const query = c.req.query('q'); + const page = c.req.query('page') || '1'; + const limit = c.req.query('limit') || '10'; + + return c.json({ query, page, limit }); +}); +// GET /search?q=hello&page=2 → { query: "hello", page: "2", limit: "10" } +``` + +## Calling Agents + +Invoke agents from routes using `c.agent`: + +```typescript +router.post('/chat', async (c) => { + const { message } = await c.req.json(); + + const response = await c.agent.chatAgent.run({ message }); + + return c.json(response); +}); +``` + +For background processing, use `c.waitUntil()`: + +```typescript +router.post('/webhook', async (c) => { + const payload = await c.req.json(); + + // Process in background, respond immediately + c.waitUntil(async () => { + await c.agent.webhookProcessor.run(payload); + }); + + return c.json({ status: 'accepted' }); +}); +``` + +## Request Validation + +Use `zValidator` middleware for type-safe validation: + +```typescript +import { createRouter } from '@agentuity/runtime'; +import { zValidator } from '@hono/zod-validator'; +import { z } from 'zod'; + +const router = createRouter(); + +const createUserSchema = z.object({ + email: z.string().email(), + name: z.string().min(1), +}); + +router.post('/users', + zValidator('json', createUserSchema), + async (c) => { + const data = c.req.valid('json'); // Typed as { email: string, name: string } + const user = await c.agent.userCreator.run(data); + return c.json(user); + } +); + +export default router; +``` + +## Request Context + +The context object (`c`) provides access to request data and Agentuity services: + +**Request data:** +```typescript +await c.req.json(); // Parse JSON body +await c.req.text(); // Get raw text body +c.req.param('id'); // Route parameter +c.req.query('page'); // Query string +c.req.header('Authorization'); // Request header +``` + +**Responses:** +```typescript +c.json({ data }); // JSON response +c.text('OK'); // Plain text +c.html('

Hello

'); // HTML response +c.redirect('/other'); // Redirect +``` + +**Agentuity services:** +```typescript +c.agent.myAgent.run(input); // Call an agent +c.kv.get('bucket', 'key'); // Key-value storage +c.vector.search('ns', opts); // Vector search +c.var.logger.info('message'); // Logging +``` + +## Best Practices + +### Validate input + +Always validate request bodies, especially for public endpoints: + +```typescript +router.post('/api', + zValidator('json', inputSchema), + async (c) => { + const data = c.req.valid('json'); + // data is guaranteed valid + } +); +``` + +### Use structured logging + +Use `c.var.logger` instead of `console.log` for searchable, traceable logs: + +```typescript +c.var.logger.info('Request processed', { userId, duration: Date.now() - start }); +c.var.logger.error('Processing failed', { error: err.message }); +``` + +### Order routes correctly + +Register specific routes before generic ones: + +```typescript +// Correct: specific before generic +router.get('/users/me', getCurrentUser); +router.get('/users/:id', getUserById); + +// Wrong: :id matches "me" first +router.get('/users/:id', getUserById); +router.get('/users/me', getCurrentUser); // Never reached +``` + +## Next Steps + +- [Scheduled Jobs (Cron)](/Building/Routes-Triggers/cron) — Run tasks on a schedule +- [Email Handling](/Building/Routes-Triggers/email) — Process incoming emails +- [WebSockets](/Building/Routes-Triggers/websockets) — Real-time bidirectional communication +- [Server-Sent Events](/Building/Routes-Triggers/sse) — Stream updates to clients diff --git a/content/v1/Building/Routes-Triggers/meta.json b/content/v1/Building/Routes-Triggers/meta.json new file mode 100644 index 00000000..8f9ed461 --- /dev/null +++ b/content/v1/Building/Routes-Triggers/meta.json @@ -0,0 +1,11 @@ +{ + "title": "Routes & Triggers", + "pages": [ + "http-routes", + "cron", + "email", + "sms", + "websockets", + "sse" + ] +} diff --git a/content/v1/Building/Routes-Triggers/sms.mdx b/content/v1/Building/Routes-Triggers/sms.mdx new file mode 100644 index 00000000..526a8bd9 --- /dev/null +++ b/content/v1/Building/Routes-Triggers/sms.mdx @@ -0,0 +1,127 @@ +--- +title: SMS Handling +description: Process incoming text messages with router.sms() +--- + +Handle incoming SMS messages sent to a phone number. Phone numbers are configured in code and integrated with Twilio automatically when deployed. + +## Basic Example + +```typescript +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +router.sms({ number: '+15551234567' }, async (c) => { + // Parse Twilio payload + const formData = await c.req.parseBody(); + const from = formData['From'] as string; + const body = formData['Body'] as string; + + const reply = await c.agent.smsBot.run({ + sender: from, + message: body, + }); + + // Return value is sent as SMS reply + return c.text(reply); +}); + +export default router; +``` + +## Parsing Twilio Payloads + +Twilio sends webhooks in form-encoded format by default, but can also send JSON: + +```typescript +router.sms({ number: '+15551234567' }, async (c) => { + let from = 'unknown'; + let body = ''; + + const contentType = c.req.header('content-type') || ''; + + if (contentType.includes('application/x-www-form-urlencoded')) { + // Default Twilio format + const formData = await c.req.parseBody(); + from = formData['From'] as string; + body = formData['Body'] as string; + } else if (contentType.includes('application/json')) { + // JSON format (if configured) + const data = await c.req.json(); + from = data.From || data.from; + body = data.Body || data.message; + } + + const reply = await c.agent.smsHandler.run({ from, body }); + return c.text(reply); +}); +``` + +## Full Example + +An SMS bot that handles commands and provides help: + +```typescript +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +router.sms({ number: '+15551234567' }, async (c) => { + try { + const formData = await c.req.parseBody(); + const from = formData['From'] as string; + const body = (formData['Body'] as string).trim().toLowerCase(); + + c.var.logger.info('SMS received', { from, body }); + + // Handle commands + if (body === 'help') { + return c.text('Commands: STATUS, BALANCE, HELP'); + } + + if (body === 'status') { + const status = await c.agent.statusChecker.run({ userId: from }); + return c.text(`Your status: ${status.message}`); + } + + if (body === 'balance') { + const balance = await c.agent.balanceChecker.run({ phone: from }); + return c.text(`Balance: $${balance.amount}`); + } + + // Default response + return c.text('Unknown command. Text HELP for options.'); + } catch (error) { + c.var.logger.error('SMS processing failed', { error }); + return c.text('Sorry, something went wrong. Try again later.'); + } +}); + +export default router; +``` + +## Phone Number Format + +Phone numbers must use E.164 international format: + +| Format | Example | +|--------|---------| +| US | `+15551234567` | +| UK | `+447911123456` | +| Germany | `+4915112345678` | + +## How Replies Work + +The text you return from the handler is automatically sent as an SMS reply to the sender. Agentuity handles the Twilio TwiML response format for you. + +```typescript +// This text is sent back to the sender as an SMS +return c.text('Thanks for your message!'); +``` + +## Next Steps + +- [Email Handling](/Building/Routes-Triggers/email) — Process incoming emails +- [WebSockets](/Building/Routes-Triggers/websockets) — Real-time bidirectional communication +- [HTTP Routes](/Building/Routes-Triggers/http-routes) — Standard API endpoints diff --git a/content/v1/Building/Routes-Triggers/sse.mdx b/content/v1/Building/Routes-Triggers/sse.mdx new file mode 100644 index 00000000..17ca8787 --- /dev/null +++ b/content/v1/Building/Routes-Triggers/sse.mdx @@ -0,0 +1,218 @@ +--- +title: Server-Sent Events (SSE) +description: Stream updates from server to client with router.sse() +--- + + +Server-Sent Events support is new in the v1 SDK, providing efficient one-way streaming from server to client. + + +Stream real-time updates to clients over a persistent HTTP connection. Ideal for progress indicators, live feeds, and LLM response streaming. + +## Basic Example + +```typescript +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +router.sse('/updates', (c) => async (stream) => { + await stream.write('Connected!'); + + // Stream data to client + for (let i = 0; i < 5; i++) { + await stream.write(`Update ${i + 1}`); + await new Promise((r) => setTimeout(r, 1000)); + } + + stream.close(); +}); + +export default router; +``` + +## Handler Structure + +The SSE handler uses an async callback pattern: + +```typescript +router.sse('/path', (c) => async (stream) => { + // c - Hono context (available in closure) + // stream - SSE stream object + + await stream.write('data'); // Simple write + await stream.writeSSE({ event, data, id }); // Full SSE format + stream.onAbort(() => { /* cleanup */ }); + stream.close(); +}); +``` + +## Two Write APIs + +### Simple Write + +```typescript +await stream.write('Hello'); +await stream.write(JSON.stringify({ status: 'ok' })); +``` + +Automatically formats data as SSE. + +### Full SSE Format + +```typescript +await stream.writeSSE({ + event: 'status', // Event type for client filtering + data: 'Processing...', // The payload + id: '1', // Optional event ID +}); +``` + +Use this for named events that clients can filter. + +## Named Events + +Clients can listen for specific event types: + +**Server:** +```typescript +await stream.writeSSE({ event: 'progress', data: '50%' }); +await stream.writeSSE({ event: 'complete', data: JSON.stringify({ success: true }) }); +``` + +**Client:** +```javascript +const source = new EventSource('/agent-name'); + +source.addEventListener('progress', (e) => { + console.log('Progress:', e.data); +}); + +source.addEventListener('complete', (e) => { + console.log('Done:', JSON.parse(e.data)); + source.close(); +}); +``` + +## Full Example + +A job progress tracker that streams status updates: + +```typescript +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +router.sse('/', (c) => async (stream) => { + c.var.logger.info('Client connected'); + + const steps = [ + 'Loading resources...', + 'Processing data...', + 'Generating report...', + 'Finalizing...', + ]; + + let stepIndex = 0; + + const interval = setInterval(async () => { + try { + if (stepIndex < steps.length) { + const progress = ((stepIndex + 1) / steps.length * 100).toFixed(0); + + await stream.writeSSE({ + event: 'status', + data: `[${progress}%] ${steps[stepIndex]}`, + id: String(stepIndex), + }); + + stepIndex++; + } else { + await stream.write(JSON.stringify({ success: true })); + clearInterval(interval); + stream.close(); + } + } catch (error) { + c.var.logger.error('Stream error', { error }); + clearInterval(interval); + } + }, 1000); + + stream.onAbort(() => { + c.var.logger.info('Client disconnected'); + clearInterval(interval); + }); + + // Keep connection open + await new Promise(() => {}); +}); + +export default router; +``` + +## Client Disconnection + +Handle early client disconnection with `onAbort`: + +```typescript +stream.onAbort(() => { + clearInterval(interval); + // Cancel any pending work +}); +``` + +Always clean up resources to prevent memory leaks. + +## Keeping the Connection Open + +SSE connections stay open until closed. Use a pending promise to keep the handler alive: + +```typescript +router.sse('/stream', (c) => async (stream) => { + // Set up intervals, subscriptions, etc. + + // Keep connection open until client disconnects or stream.close() + await new Promise(() => {}); +}); +``` + +## Client Connection + +Connect from JavaScript using the EventSource API: + +```javascript +const source = new EventSource('https://your-project.agentuity.cloud/agent-name'); + +source.onmessage = (event) => { + console.log('Received:', event.data); +}; + +source.onerror = () => { + console.log('Connection error or closed'); + source.close(); +}; +``` + +Or with cURL: + +```bash +curl -N https://your-project.agentuity.cloud/agent-name +``` + +## SSE vs WebSocket + +| Aspect | SSE | WebSocket | +|--------|-----|-----------| +| Direction | Server → Client only | Bidirectional | +| Protocol | HTTP | WebSocket | +| Reconnection | Built-in auto-reconnect | Manual | +| Browser support | Native EventSource | Native WebSocket | +| Best for | Progress, feeds, LLM streaming | Chat, collaboration | + +Use SSE when you only need to push data **from server to client**. Use [WebSockets](/Building/Routes-Triggers/websockets) when you need **bidirectional** communication. + +## Next Steps + +- [WebSockets](/Building/Routes-Triggers/websockets) — Bidirectional real-time communication +- [HTTP Routes](/Building/Routes-Triggers/http-routes) — Standard request/response endpoints +- [React Hooks](/Building/Frontend/react-hooks) — Connect from React with `useAgentEventStream` diff --git a/content/v1/Building/Routes-Triggers/websockets.mdx b/content/v1/Building/Routes-Triggers/websockets.mdx new file mode 100644 index 00000000..53ec257a --- /dev/null +++ b/content/v1/Building/Routes-Triggers/websockets.mdx @@ -0,0 +1,181 @@ +--- +title: WebSockets +description: Real-time bidirectional communication with router.websocket() +--- + + +WebSocket support is new in the v1 SDK, enabling real-time bidirectional communication for chat interfaces, live dashboards, and collaborative tools. + + +Create persistent connections where both client and server can send messages at any time. + +## Basic Example + +```typescript +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +router.websocket('/chat', (c) => (ws) => { + ws.onOpen(() => { + c.var.logger.info('Client connected'); + ws.send('Connected!'); + }); + + ws.onMessage(async (event) => { + const message = event.data as string; + const response = await c.agent.chatAgent.run({ message }); + ws.send(response); + }); + + ws.onClose(() => { + c.var.logger.info('Client disconnected'); + }); +}); + +export default router; +``` + +## Handler Structure + +The WebSocket handler uses a callback pattern: + +```typescript +router.websocket('/path', (c) => (ws) => { + // c - Hono context (available in closure) + // ws - WebSocket connection object + + ws.onOpen(() => { /* connection opened */ }); + ws.onMessage(async (event) => { /* message received */ }); + ws.onClose(() => { /* connection closed */ }); +}); +``` + +## WebSocket Events + +| Event | Trigger | Use Case | +|-------|---------|----------| +| `onOpen` | Connection established | Send welcome message, initialize state | +| `onMessage` | Client sends data | Process messages, call agents | +| `onClose` | Connection ends | Clean up resources | + +## Server Push + +Send data to the client without waiting for a request: + +```typescript +router.websocket('/notifications', (c) => (ws) => { + let heartbeat: Timer; + + ws.onOpen(() => { + ws.send(JSON.stringify({ type: 'connected' })); + + // Push updates every 5 seconds + heartbeat = setInterval(() => { + ws.send(JSON.stringify({ + type: 'heartbeat', + time: new Date().toISOString(), + })); + }, 5000); + }); + + ws.onClose(() => { + clearInterval(heartbeat); // Clean up! + }); +}); +``` + +## Full Example + +A real-time echo server with heartbeat: + +```typescript +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +router.websocket('/', (c) => (ws) => { + let heartbeat: Timer; + + ws.onOpen(() => { + c.var.logger.info('WebSocket connected'); + ws.send('Connected! Send a message to echo it back.'); + + heartbeat = setInterval(() => { + ws.send(`Ping: ${new Date().toLocaleTimeString()}`); + }, 5000); + }); + + ws.onMessage(async (event) => { + try { + const message = event.data as string; + c.var.logger.info('Message received', { message }); + + const response = await c.agent.echoAgent.run(message); + ws.send(response); + } catch (error) { + c.var.logger.error('Message processing failed', { error }); + ws.send(JSON.stringify({ error: 'Processing failed' })); + } + }); + + ws.onClose(() => { + c.var.logger.info('WebSocket disconnected'); + clearInterval(heartbeat); + }); +}); + +export default router; +``` + +## Resource Cleanup + +Always clean up intervals, subscriptions, or other resources in `onClose`: + +```typescript +ws.onClose(() => { + clearInterval(heartbeat); // Prevent memory leaks + subscription.unsubscribe(); +}); +``` + +Failing to clean up can cause memory leaks and unexpected behavior. + +## Client Connection + +Connect from a browser or any WebSocket client: + +```javascript +const ws = new WebSocket('wss://your-project.agentuity.cloud/agent-name'); + +ws.onopen = () => { + console.log('Connected'); + ws.send('Hello!'); +}; + +ws.onmessage = (event) => { + console.log('Received:', event.data); +}; + +ws.onclose = () => { + console.log('Disconnected'); +}; +``` + +## When to Use WebSockets + +| Use Case | WebSocket | SSE | HTTP | +|----------|-----------|-----|------| +| Chat / messaging | ✓ | | | +| Live collaboration | ✓ | | | +| Real-time dashboards | ✓ | ✓ | | +| Progress updates | | ✓ | | +| Request/response API | | | ✓ | + +Use WebSockets when you need **bidirectional** communication. For **server-to-client only** streaming, consider [Server-Sent Events](/Building/Routes-Triggers/sse). + +## Next Steps + +- [Server-Sent Events](/Building/Routes-Triggers/sse) — One-way streaming from server to client +- [HTTP Routes](/Building/Routes-Triggers/http-routes) — Standard request/response endpoints +- [React Hooks](/Building/Frontend/react-hooks) — Connect from React with `useAgentWebsocket` diff --git a/content/v1/Building/meta.json b/content/v1/Building/meta.json new file mode 100644 index 00000000..3a0fc682 --- /dev/null +++ b/content/v1/Building/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Building", + "pages": ["Agents", "Routes-Triggers"] +} diff --git a/content/v1/Getting-Started/app-configuration.mdx b/content/v1/Getting-Started/app-configuration.mdx new file mode 100644 index 00000000..3f843274 --- /dev/null +++ b/content/v1/Getting-Started/app-configuration.mdx @@ -0,0 +1,122 @@ +--- +title: App Configuration +description: Configure your Agentuity project +--- + +Agentuity projects use minimal configuration. Most setup happens in code, not config files. + +## agentuity.json + +The project configuration file: + +```json +{ + "name": "my-project", + "orgId": "org_...", + "projectId": "proj_..." +} +``` + +No agent definitions, no trigger configurations. Those live in your code. + +## app.ts + +The app entry point configures your application: + +```typescript +import { createApp } from '@agentuity/runtime'; + +const app = createApp(); + +export default app.server; +``` + +### With Options + +```typescript +import { createApp } from '@agentuity/runtime'; + +const app = createApp({ + // CORS configuration + cors: { + origin: ['https://myapp.com'], + credentials: true, + }, + + // Custom storage implementations (optional) + services: { + keyvalue: myCustomKV, + vector: myCustomVector, + }, +}); + +// App-level event listeners +app.addEventListener('agent.started', (event, agent, ctx) => { + app.logger.info('Agent started', { name: agent.metadata.name }); +}); + +app.addEventListener('agent.completed', (event, agent, ctx) => { + app.logger.info('Agent completed', { session: ctx.sessionId }); +}); + +export default app.server; +``` + +## Environment Variables + +```bash +# Required +AGENTUITY_SDK_KEY=... # API key for Agentuity services + +# Optional +AGENTUITY_LOG_LEVEL=info # trace, debug, info, warn, error +AGENTUITY_PORT=3500 # Dev server port (default: 3500) + +# LLM Provider Keys (optional; if using your own API keys instead of the AI Gateway) +OPENAI_API_KEY=sk-... +ANTHROPIC_API_KEY=sk-ant-... +``` + + +If you don't set provider API keys, LLM requests are routed through the Agentuity AI Gateway using your SDK key. This provides unified billing and monitoring. + + +## Infrastructure as Code + +Unlike traditional platforms, Agentuity defines infrastructure in your route files: + +```typescript +// src/agents/scheduler/route.ts +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +// Cron job - runs every hour +router.cron('0 * * * *', async (c) => { + await c.agent.scheduler.run({ task: 'cleanup' }); + return c.text('OK'); +}); + +// Email trigger +router.email('support@mycompany.com', async (email, c) => { + const result = await c.agent.emailHandler.run({ + from: email.fromEmail(), + subject: email.subject(), + body: email.text(), + }); + return c.text('Processed'); +}); + +export default router; +``` + +This approach means: +- **Deployments are self-contained** — rolling back restores exact configuration +- **Version control** — your infrastructure changes are tracked in Git +- **No config drift** — what's in code is what runs + +## Next Steps + +- [HTTP Routes](/Building/Routes-Triggers/http-routes) — Define HTTP endpoints +- [Cron Jobs](/Building/Routes-Triggers/cron) — Schedule recurring tasks +- [AI Gateway](/Building/Agents/ai-gateway) — Configure LLM providers diff --git a/content/v1/Getting-Started/installation.mdx b/content/v1/Getting-Started/installation.mdx new file mode 100644 index 00000000..3486a52f --- /dev/null +++ b/content/v1/Getting-Started/installation.mdx @@ -0,0 +1,48 @@ +--- +title: Installation +description: Set up your development environment +--- + +## Prerequisites + +- **Node.js 18+** or **Bun** (recommended) +- A code editor + +## Install the CLI + +```bash +npm install -g @agentuity/cli +``` + +Or with Bun (recommended): +TODO: not sure our stuff works with anything BUT Bun...need to double check this, but I think we're "tied to Bun" as my CEO said recently. + +```bash +bun install -g @agentuity/cli +``` + +## Create a Project + +```bash +agentuity new my-project +cd my-project +``` + +The CLI will prompt you to select a template and configure your project. + +## Start the Dev Server + +```bash +bun dev +``` + +Your project is now running at `http://localhost:3500`. + + +The dev server includes hot reload, so changes to your agents and routes are reflected immediately. + + +## Next Steps + +- [Quickstart](/Getting-Started/quickstart) — Build your first agent +- [Project Structure](/Getting-Started/project-structure) — Understand the file layout diff --git a/content/v1/Getting-Started/meta.json b/content/v1/Getting-Started/meta.json new file mode 100644 index 00000000..d5e69715 --- /dev/null +++ b/content/v1/Getting-Started/meta.json @@ -0,0 +1,10 @@ +{ + "title": "Getting Started", + "pages": [ + "what-is-agentuity", + "installation", + "quickstart", + "project-structure", + "app-configuration" + ] +} diff --git a/content/v1/Getting-Started/project-structure.mdx b/content/v1/Getting-Started/project-structure.mdx new file mode 100644 index 00000000..7a942bf4 --- /dev/null +++ b/content/v1/Getting-Started/project-structure.mdx @@ -0,0 +1,119 @@ +--- +title: Project Structure +description: Understand how Agentuity projects are organized +--- + +Agentuity projects follow a convention-based structure that enables automatic discovery and deployment. + +## Directory Layout + +``` +my-project/ +├── agentuity.json # Project configuration +├── .env # Environment variables +├── package.json # Dependencies +├── app.ts # App entry point +└── src/ + ├── agents/ # Agent code (required) + │ └── chat/ + │ ├── agent.ts # Business logic + │ └── route.ts # HTTP routing + ├── apis/ # Pure HTTP endpoints (optional) + │ └── health/ + │ └── route.ts + └── web/ # Frontend code (scaffolded by default) + └── App.tsx +``` + +Agent names come from their directory. `src/agents/chat/` creates an agent accessible as `c.agent.chat`. + +## The Two-File Pattern + +Every agent has two files. Both are required for agents. For HTTP-only endpoints without agent logic, use `src/apis/` with just a `route.ts`. + +| File | Purpose | Creates | +|------|---------|---------| +| `agent.ts` | Business logic, schemas, metadata | `createAgent()` | +| `route.ts` | HTTP endpoints, triggers | `createRouter()` | + +This separation keeps concerns clear—agents focus on logic, routes handle HTTP. + +### agent.ts + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ message: z.string() }), + output: z.object({ response: z.string() }), + }, + handler: async (ctx, input) => { + return { response: `Echo: ${input.message}` }; + }, +}); + +export default agent; +``` + +### route.ts + +```typescript +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +router.post('/', async (c) => { + const body = await c.req.json(); + const result = await c.agent.chat.run(body); + return c.json(result); +}); + +export default router; +``` + +The route calls the agent via `c.agent..run()`. The name comes from the directory, so `src/agents/chat/` becomes `c.agent.chat`, for example. + +## APIs vs Agents + +| | Agents (`src/agents/`) | APIs (`src/apis/`) | +|---|---|---| +| **Files** | `agent.ts` + `route.ts` | Just `route.ts` | +| **Schema validation** | Built-in | Manual | +| **Events & Evals** | Yes | No | +| **Storage access** | Full | Full | +| **Best for** | Core business logic | Health checks, webhooks, simple endpoints | + +Use APIs when you need a lightweight HTTP endpoint without the agent overhead. + +## Frontend (`src/web/`) + +Place frontend code in `src/web/` to deploy alongside your agents. The CLI bundles and serves it automatically. + +React is currently supported via `@agentuity/react`, with a framework-agnostic approach that enables support for Next.js, Svelte, and others. + +```typescript +// src/web/App.tsx +import { useAgent } from '@agentuity/react'; + +function App() { + const { data, run, running } = useAgent('chat'); + + return ( + + ); +} +``` + + +You can also deploy your frontend elsewhere (e.g.,Vercel, Netlify) and call your Agentuity agents via the React hooks with a configured `baseUrl`. + + +## Next Steps + +- [App Configuration](/Getting-Started/app-configuration) — Configure `app.ts` and `agentuity.json` +- [Creating Agents](/Building/Agents/creating-agents) — Deep dive into agent creation +- [HTTP Routes](/Building/Routes-Triggers/http-routes) — Learn about routing options diff --git a/content/v1/Getting-Started/quickstart.mdx b/content/v1/Getting-Started/quickstart.mdx new file mode 100644 index 00000000..ac7b1710 --- /dev/null +++ b/content/v1/Getting-Started/quickstart.mdx @@ -0,0 +1,144 @@ +--- +title: Quickstart +description: Build your first agent in 5 minutes +--- + +This guide walks you through creating a simple chat agent that uses OpenAI to generate responses. + +## 1. Create the Agent + +Create `src/agents/chat/agent.ts`: + +```typescript +import { createAgent, type AgentContext } from '@agentuity/runtime'; +import { generateText } from 'ai'; +import { openai } from '@ai-sdk/openai'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ message: z.string() }), + output: z.object({ response: z.string() }), + }, + handler: async (ctx: AgentContext, input) => { + ctx.logger.info('Received message', { message: input.message }); + + const { text } = await generateText({ + model: openai('gpt-5-mini'), + prompt: input.message, + }); + + return { response: text }; + }, +}); + +export default agent; +``` + +## 2. Add a Route + +Create `src/agents/chat/route.ts`: + +```typescript +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +router.post('/', async (c) => { + const body = await c.req.json(); + const result = await c.agent.chat.run(body); + return c.json(result); +}); + +export default router; +``` + +## 3. Test It + +Start the dev server: + +```bash +bun dev +``` + +Send a request: + +```bash +curl -X POST http://localhost:3500/chat \ + -H "Content-Type: application/json" \ + -d '{"message": "What is the capital of France?"}' +``` + +Response: + +```json +{ + "response": "The capital of France is Paris." +} +``` + +## 4. Add Streaming + +For real-time responses, return a stream instead: + +```typescript +import { createAgent, type AgentContext } from '@agentuity/runtime'; +import { streamText } from 'ai'; +import { openai } from '@ai-sdk/openai'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ message: z.string() }), + stream: true, + }, + handler: async (ctx: AgentContext, input) => { + const { textStream } = streamText({ + model: openai('gpt-5-mini'), + prompt: input.message, + }); + + return textStream; + }, +}); + +export default agent; +``` + +Update the route to use the streaming endpoint: + +```typescript +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +router.stream('/', async (c) => { + const body = await c.req.json(); + return c.agent.chat.run(body); +}); + +export default router; +``` + +## 5. Deploy + +When you're ready, deploy to Agentuity: + +```bash +agentuity deploy +``` + +Your agent is now live with a public URL. + +## What's Next? + +**Build something more:** + +- [Build a multi-agent system](/Building/Agents/calling-other-agents) — Routing, RAG, workflows +- [Add a React frontend](/Building/Frontend/react-hooks) — Call your agent from the web +- [Persist data](/Building/Agents/state-management) — Use thread and session state + +**Understand the platform:** + +- [Project Structure](/Getting-Started/project-structure) — The two-file pattern +- [App Configuration](/Getting-Started/app-configuration) — Configure your project diff --git a/content/v1/Getting-Started/what-is-agentuity.mdx b/content/v1/Getting-Started/what-is-agentuity.mdx new file mode 100644 index 00000000..220a2ff7 --- /dev/null +++ b/content/v1/Getting-Started/what-is-agentuity.mdx @@ -0,0 +1,66 @@ +--- +title: What is Agentuity? +description: The full-stack platform for AI agents +--- + +
+ Build agents, not infrastructure +
+ +Agentuity is a full-stack platform for building, deploying, and operating AI agents. + +Write TypeScript, define routes, and deploy with a single command. We handle the infrastructure, from automatic scaling to built-in observability and more. + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { generateText } from 'ai'; +import { openai } from '@ai-sdk/openai'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ message: z.string() }), + output: z.object({ response: z.string() }), + }, + handler: async (ctx, input) => { + const { text } = await generateText({ + model: openai('gpt-5-mini'), + prompt: input.message, + }); + return { response: text }; + }, +}); + +export default agent; +``` + +This agent uses the AI SDK to call OpenAI and respond to messages. Deploy with `agentuity deploy` and it scales automatically. Call it from any frontend, CLI, or other agent. + +## What You Get + +- **Routes & Triggers** — HTTP, WebSocket, SSE, cron, email, SMS +- **Storage** — Key-value, vector, object storage, durable streams +- **Observability** — Logging, tracing, real-time analytics +- **[AI Gateway](/Building/Agents/ai-gateway)** — Route LLM calls through OpenAI, Anthropic, Google, and more +- **Evaluations** — Automated quality checks that run after each agent execution +- **Frontend** — Deploy React apps alongside your agents + +## How It Works + +Each agent has two files: + +| File | Purpose | +|------|---------| +| `agent.ts` | Business logic with schema validation | +| `route.ts` | HTTP routing (built on Hono) | + +Infrastructure like cron schedules and email handlers are defined in code, not config. Rolling back a deployment restores the exact configuration from that version. + +--- + +

We're building for a future where agents are the primary way to build and operate software, and where infrastructure is purpose-built for this new paradigm.

+ +## Next Steps + +- [Installation](/Getting-Started/installation) — Set up your environment +- [Quickstart](/Getting-Started/quickstart) — Build your first agent in 5 minutes diff --git a/content/v1/Guides/vector-storage.mdx b/content/v1/Guides/vector-storage.mdx index 77908db8..73e44742 100644 --- a/content/v1/Guides/vector-storage.mdx +++ b/content/v1/Guides/vector-storage.mdx @@ -564,7 +564,7 @@ const ragAgent = createAgent({ // 4. Generate response using context const { text } = await generateText({ - model: openai('gpt-4o-mini'), + model: openai('gpt-5-mini'), prompt: `Answer the question based on the following context: Context: ${assembledContext} diff --git a/content/v1/Guides/agent-communication.mdx b/content/v1/archive/agent-communication.mdx similarity index 100% rename from content/v1/Guides/agent-communication.mdx rename to content/v1/archive/agent-communication.mdx diff --git a/content/v1/Introduction/architecture.mdx b/content/v1/archive/architecture.mdx similarity index 100% rename from content/v1/Introduction/architecture.mdx rename to content/v1/archive/architecture.mdx diff --git a/content/v1/Guides/context-types.mdx b/content/v1/archive/context-types.mdx similarity index 100% rename from content/v1/Guides/context-types.mdx rename to content/v1/archive/context-types.mdx diff --git a/content/v1/Guides/evaluations.mdx b/content/v1/archive/evaluations.mdx similarity index 100% rename from content/v1/Guides/evaluations.mdx rename to content/v1/archive/evaluations.mdx diff --git a/content/v1/Guides/events.mdx b/content/v1/archive/events.mdx similarity index 100% rename from content/v1/Guides/events.mdx rename to content/v1/archive/events.mdx diff --git a/content/v1/Introduction/introduction.mdx b/content/v1/archive/introduction.mdx similarity index 100% rename from content/v1/Introduction/introduction.mdx rename to content/v1/archive/introduction.mdx diff --git a/content/v1/Guides/routing-triggers.mdx b/content/v1/archive/routing-triggers.mdx similarity index 100% rename from content/v1/Guides/routing-triggers.mdx rename to content/v1/archive/routing-triggers.mdx diff --git a/content/v1/Guides/schema-validation.mdx b/content/v1/archive/schema-validation.mdx similarity index 100% rename from content/v1/Guides/schema-validation.mdx rename to content/v1/archive/schema-validation.mdx diff --git a/content/v1/Guides/sessions-threads.mdx b/content/v1/archive/sessions-threads.mdx similarity index 100% rename from content/v1/Guides/sessions-threads.mdx rename to content/v1/archive/sessions-threads.mdx diff --git a/content/v1/Guides/subagents.mdx b/content/v1/archive/subagents-old.mdx similarity index 100% rename from content/v1/Guides/subagents.mdx rename to content/v1/archive/subagents-old.mdx diff --git a/content/v1/meta.json b/content/v1/meta.json index 08faecc7..a8822f36 100644 --- a/content/v1/meta.json +++ b/content/v1/meta.json @@ -1,11 +1,7 @@ { "title": "v1 Documentation", "pages": [ - "Introduction", - "Guides", - "SDK", - "migration-guide", - "Examples", - "Training" + "Getting-Started", + "Building" ] } \ No newline at end of file From f20b8e9c180b804ace99828d701f550508eac4ca Mon Sep 17 00:00:00 2001 From: parteeksingh24 Date: Wed, 26 Nov 2025 16:00:17 -0800 Subject: [PATCH 12/63] Add version picker --- app/(docs)/layout.tsx | 4 ++ app/layout.config.tsx | 2 +- components/V0DeprecationBanner.tsx | 28 +++++++++++ content/Changelog/index.mdx | 12 ----- content/Cloud/index.mdx | 28 ----------- content/meta.json | 12 +---- content/{ => v0}/CLI/agent.mdx | 0 content/{ => v0}/CLI/apikey.mdx | 0 content/{ => v0}/CLI/auth.mdx | 0 content/{ => v0}/CLI/bundle.mdx | 0 content/{ => v0}/CLI/cloud.mdx | 0 content/{ => v0}/CLI/dev.mdx | 2 +- content/{ => v0}/CLI/env.mdx | 2 +- content/{ => v0}/CLI/installation.mdx | 2 +- content/{ => v0}/CLI/mcp.mdx | 0 content/{ => v0}/CLI/meta.json | 0 content/{ => v0}/CLI/project.mdx | 0 content/{ => v0}/CLI/version.mdx | 0 content/{ => v0}/Changelog/cli.mdx | 0 content/v0/Changelog/index.mdx | 12 +++++ content/{ => v0}/Changelog/meta.json | 0 content/{ => v0}/Changelog/sdk-js.mdx | 0 content/{ => v0}/Changelog/sdk-py.mdx | 0 content/{ => v0}/Cloud/agents.mdx | 4 +- content/{ => v0}/Cloud/aigateway.mdx | 0 content/{ => v0}/Cloud/api-keys.mdx | 2 +- content/v0/Cloud/index.mdx | 28 +++++++++++ content/{ => v0}/Cloud/key-value-memory.mdx | 0 content/{ => v0}/Cloud/meta.json | 0 content/{ => v0}/Cloud/object-storage.mdx | 0 content/{ => v0}/Cloud/organization.mdx | 0 content/{ => v0}/Cloud/project.mdx | 2 +- content/{ => v0}/Cloud/settings.mdx | 0 content/{ => v0}/Cloud/vector-memory.mdx | 0 content/{ => v0}/Examples/index.mdx | 0 content/{ => v0}/Guides/.agent-patterns.mdx | 0 content/{ => v0}/Guides/.security.mdx | 0 .../{ => v0}/Guides/agent-communication.mdx | 0 .../{ => v0}/Guides/agent-data-handling.mdx | 2 +- content/{ => v0}/Guides/agent-engineering.mdx | 4 +- content/{ => v0}/Guides/agent-io.mdx | 2 +- content/{ => v0}/Guides/agent-logging.mdx | 2 +- .../{ => v0}/Guides/agent-native-cloud.mdx | 2 +- content/{ => v0}/Guides/agent-streaming.mdx | 2 +- content/{ => v0}/Guides/agent-telemetry.mdx | 2 +- content/{ => v0}/Guides/agent-tracing.mdx | 2 +- content/{ => v0}/Guides/ai-gateway.mdx | 2 +- content/{ => v0}/Guides/devmode.mdx | 4 +- content/{ => v0}/Guides/key-value.mdx | 8 ++-- content/{ => v0}/Guides/meta.json | 0 content/{ => v0}/Guides/object-storage.mdx | 6 +-- content/{ => v0}/Guides/vector-db.mdx | 14 +++--- content/{ => v0}/Guides/what-is-an-agent.mdx | 4 +- .../{ => v0}/Introduction/architecture.mdx | 0 .../{ => v0}/Introduction/getting-started.mdx | 2 +- content/{ => v0}/Introduction/index.mdx | 4 +- .../{ => v0}/Introduction/kitchen-sink.mdx | 0 content/{ => v0}/Introduction/meta.json | 0 content/{ => v0}/Introduction/templates.mdx | 0 content/{ => v0}/SDKs/index.mdx | 0 .../SDKs/javascript/api-reference.mdx | 0 .../SDKs/javascript/core-concepts.mdx | 0 .../SDKs/javascript/error-handling.mdx | 0 .../SDKs/javascript/examples/index.mdx | 0 .../SDKs/javascript/examples/langchain.mdx | 0 .../{ => v0}/SDKs/javascript/frameworks.mdx | 0 content/{ => v0}/SDKs/javascript/index.mdx | 14 +++--- content/{ => v0}/SDKs/javascript/llm.mdx | 0 content/{ => v0}/SDKs/javascript/meta.json | 0 .../SDKs/javascript/troubleshooting.mdx | 0 content/{ => v0}/SDKs/meta.json | 0 .../{ => v0}/SDKs/python/api-reference.mdx | 0 content/{ => v0}/SDKs/python/async-api.mdx | 0 .../{ => v0}/SDKs/python/core-concepts.mdx | 0 .../{ => v0}/SDKs/python/data-handling.mdx | 0 .../{ => v0}/SDKs/python/examples/index.mdx | 0 .../SDKs/python/examples/pydantic.mdx | 0 content/{ => v0}/SDKs/python/frameworks.mdx | 0 content/{ => v0}/SDKs/python/index.mdx | 10 ++-- content/{ => v0}/SDKs/python/llm.mdx | 0 content/{ => v0}/SDKs/python/meta.json | 0 .../error-codes/authentication.mdx | 0 .../Troubleshooting/error-codes/cli.mdx | 0 .../error-codes/datastores.mdx | 0 .../Troubleshooting/error-codes/index.mdx | 0 .../error-codes/integration.mdx | 0 .../Troubleshooting/error-codes/projects.mdx | 0 .../Troubleshooting/error-codes/system.mdx | 0 content/v0/meta.json | 15 ++++++ .../{Building => Build}/Agents/ai-gateway.mdx | 8 ++-- .../Agents/ai-sdk-integration.mdx | 12 ++--- .../Agents/calling-other-agents.mdx | 12 ++--- .../Agents/creating-agents.mdx | 12 ++--- .../Agents/evaluations.mdx | 10 ++-- .../Agents/events-lifecycle.mdx | 10 ++-- .../v1/{Building => Build}/Agents/meta.json | 0 .../Agents/schema-libraries.mdx | 6 +-- .../Agents/state-management.mdx | 12 ++--- .../Agents/streaming-responses.mdx | 12 ++--- .../{Building => Build}/Agents/subagents.mdx | 8 ++-- .../Routes-Triggers/cron.mdx | 22 +++++---- .../Routes-Triggers/email.mdx | 6 +-- .../Routes-Triggers/http-routes.mdx | 18 +++---- .../Routes-Triggers/meta.json | 0 .../Routes-Triggers/sms.mdx | 20 +++----- .../Routes-Triggers/sse.mdx | 26 +++++----- .../Routes-Triggers/websockets.mdx | 18 +++---- content/v1/{Building => Build}/meta.json | 2 +- .../app-configuration.mdx | 6 +-- .../installation.mdx | 4 +- .../meta.json | 2 +- .../project-structure.mdx | 6 +-- .../quickstart.mdx | 10 ++-- .../what-is-agentuity.mdx | 6 +-- content/v1/meta.json | 8 ++-- content/v1/migration-guide.mdx | 2 - next.config.mjs | 47 +++++++++++++++++-- 117 files changed, 296 insertions(+), 250 deletions(-) create mode 100644 components/V0DeprecationBanner.tsx delete mode 100644 content/Changelog/index.mdx delete mode 100644 content/Cloud/index.mdx rename content/{ => v0}/CLI/agent.mdx (100%) rename content/{ => v0}/CLI/apikey.mdx (100%) rename content/{ => v0}/CLI/auth.mdx (100%) rename content/{ => v0}/CLI/bundle.mdx (100%) rename content/{ => v0}/CLI/cloud.mdx (100%) rename content/{ => v0}/CLI/dev.mdx (90%) rename content/{ => v0}/CLI/env.mdx (98%) rename content/{ => v0}/CLI/installation.mdx (99%) rename content/{ => v0}/CLI/mcp.mdx (100%) rename content/{ => v0}/CLI/meta.json (100%) rename content/{ => v0}/CLI/project.mdx (100%) rename content/{ => v0}/CLI/version.mdx (100%) rename content/{ => v0}/Changelog/cli.mdx (100%) create mode 100644 content/v0/Changelog/index.mdx rename content/{ => v0}/Changelog/meta.json (100%) rename content/{ => v0}/Changelog/sdk-js.mdx (100%) rename content/{ => v0}/Changelog/sdk-py.mdx (100%) rename content/{ => v0}/Cloud/agents.mdx (99%) rename content/{ => v0}/Cloud/aigateway.mdx (100%) rename content/{ => v0}/Cloud/api-keys.mdx (93%) create mode 100644 content/v0/Cloud/index.mdx rename content/{ => v0}/Cloud/key-value-memory.mdx (100%) rename content/{ => v0}/Cloud/meta.json (100%) rename content/{ => v0}/Cloud/object-storage.mdx (100%) rename content/{ => v0}/Cloud/organization.mdx (100%) rename content/{ => v0}/Cloud/project.mdx (96%) rename content/{ => v0}/Cloud/settings.mdx (100%) rename content/{ => v0}/Cloud/vector-memory.mdx (100%) rename content/{ => v0}/Examples/index.mdx (100%) rename content/{ => v0}/Guides/.agent-patterns.mdx (100%) rename content/{ => v0}/Guides/.security.mdx (100%) rename content/{ => v0}/Guides/agent-communication.mdx (100%) rename content/{ => v0}/Guides/agent-data-handling.mdx (99%) rename content/{ => v0}/Guides/agent-engineering.mdx (97%) rename content/{ => v0}/Guides/agent-io.mdx (97%) rename content/{ => v0}/Guides/agent-logging.mdx (98%) rename content/{ => v0}/Guides/agent-native-cloud.mdx (99%) rename content/{ => v0}/Guides/agent-streaming.mdx (99%) rename content/{ => v0}/Guides/agent-telemetry.mdx (97%) rename content/{ => v0}/Guides/agent-tracing.mdx (98%) rename content/{ => v0}/Guides/ai-gateway.mdx (97%) rename content/{ => v0}/Guides/devmode.mdx (97%) rename content/{ => v0}/Guides/key-value.mdx (96%) rename content/{ => v0}/Guides/meta.json (100%) rename content/{ => v0}/Guides/object-storage.mdx (98%) rename content/{ => v0}/Guides/vector-db.mdx (96%) rename content/{ => v0}/Guides/what-is-an-agent.mdx (97%) rename content/{ => v0}/Introduction/architecture.mdx (100%) rename content/{ => v0}/Introduction/getting-started.mdx (94%) rename content/{ => v0}/Introduction/index.mdx (86%) rename content/{ => v0}/Introduction/kitchen-sink.mdx (100%) rename content/{ => v0}/Introduction/meta.json (100%) rename content/{ => v0}/Introduction/templates.mdx (100%) rename content/{ => v0}/SDKs/index.mdx (100%) rename content/{ => v0}/SDKs/javascript/api-reference.mdx (100%) rename content/{ => v0}/SDKs/javascript/core-concepts.mdx (100%) rename content/{ => v0}/SDKs/javascript/error-handling.mdx (100%) rename content/{ => v0}/SDKs/javascript/examples/index.mdx (100%) rename content/{ => v0}/SDKs/javascript/examples/langchain.mdx (100%) rename content/{ => v0}/SDKs/javascript/frameworks.mdx (100%) rename content/{ => v0}/SDKs/javascript/index.mdx (71%) rename content/{ => v0}/SDKs/javascript/llm.mdx (100%) rename content/{ => v0}/SDKs/javascript/meta.json (100%) rename content/{ => v0}/SDKs/javascript/troubleshooting.mdx (100%) rename content/{ => v0}/SDKs/meta.json (100%) rename content/{ => v0}/SDKs/python/api-reference.mdx (100%) rename content/{ => v0}/SDKs/python/async-api.mdx (100%) rename content/{ => v0}/SDKs/python/core-concepts.mdx (100%) rename content/{ => v0}/SDKs/python/data-handling.mdx (100%) rename content/{ => v0}/SDKs/python/examples/index.mdx (100%) rename content/{ => v0}/SDKs/python/examples/pydantic.mdx (100%) rename content/{ => v0}/SDKs/python/frameworks.mdx (100%) rename content/{ => v0}/SDKs/python/index.mdx (77%) rename content/{ => v0}/SDKs/python/llm.mdx (100%) rename content/{ => v0}/SDKs/python/meta.json (100%) rename content/{ => v0}/Troubleshooting/error-codes/authentication.mdx (100%) rename content/{ => v0}/Troubleshooting/error-codes/cli.mdx (100%) rename content/{ => v0}/Troubleshooting/error-codes/datastores.mdx (100%) rename content/{ => v0}/Troubleshooting/error-codes/index.mdx (100%) rename content/{ => v0}/Troubleshooting/error-codes/integration.mdx (100%) rename content/{ => v0}/Troubleshooting/error-codes/projects.mdx (100%) rename content/{ => v0}/Troubleshooting/error-codes/system.mdx (100%) create mode 100644 content/v0/meta.json rename content/v1/{Building => Build}/Agents/ai-gateway.mdx (93%) rename content/v1/{Building => Build}/Agents/ai-sdk-integration.mdx (92%) rename content/v1/{Building => Build}/Agents/calling-other-agents.mdx (97%) rename content/v1/{Building => Build}/Agents/creating-agents.mdx (93%) rename content/v1/{Building => Build}/Agents/evaluations.mdx (95%) rename content/v1/{Building => Build}/Agents/events-lifecycle.mdx (91%) rename content/v1/{Building => Build}/Agents/meta.json (100%) rename content/v1/{Building => Build}/Agents/schema-libraries.mdx (96%) rename content/v1/{Building => Build}/Agents/state-management.mdx (95%) rename content/v1/{Building => Build}/Agents/streaming-responses.mdx (92%) rename content/v1/{Building => Build}/Agents/subagents.mdx (97%) rename content/v1/{Building => Build}/Routes-Triggers/cron.mdx (78%) rename content/v1/{Building => Build}/Routes-Triggers/email.mdx (93%) rename content/v1/{Building => Build}/Routes-Triggers/http-routes.mdx (88%) rename content/v1/{Building => Build}/Routes-Triggers/meta.json (100%) rename content/v1/{Building => Build}/Routes-Triggers/sms.mdx (82%) rename content/v1/{Building => Build}/Routes-Triggers/sse.mdx (80%) rename content/v1/{Building => Build}/Routes-Triggers/websockets.mdx (84%) rename content/v1/{Building => Build}/meta.json (66%) rename content/v1/{Getting-Started => Get-Started}/app-configuration.mdx (92%) rename content/v1/{Getting-Started => Get-Started}/installation.mdx (84%) rename content/v1/{Getting-Started => Get-Started}/meta.json (80%) rename content/v1/{Getting-Started => Get-Started}/project-structure.mdx (92%) rename content/v1/{Getting-Started => Get-Started}/quickstart.mdx (85%) rename content/v1/{Getting-Started => Get-Started}/what-is-agentuity.mdx (88%) diff --git a/app/(docs)/layout.tsx b/app/(docs)/layout.tsx index 5900714e..4ab92a8d 100644 --- a/app/(docs)/layout.tsx +++ b/app/(docs)/layout.tsx @@ -4,6 +4,7 @@ import type { ReactNode } from 'react'; import { baseOptions } from '@/app/layout.config'; import { source } from '@/lib/source'; import AISearchToggle from '../../components/AISearchToggle'; +import { V0DeprecationBanner } from '../../components/V0DeprecationBanner'; export default function Layout({ children }: { children: ReactNode }) { return ( @@ -20,6 +21,9 @@ export default function Layout({ children }: { children: ReactNode }) { ), }, }} + sidebar={{ + banner: , + }} > {children} diff --git a/app/layout.config.tsx b/app/layout.config.tsx index b52b7e52..95bcf839 100644 --- a/app/layout.config.tsx +++ b/app/layout.config.tsx @@ -8,7 +8,7 @@ import { XButton } from '../components/XButton'; */ export const baseOptions: BaseLayoutProps = { nav: { - url: '/Introduction', + url: '/v0/Introduction', title: (
+ + + v0 is deprecated.{' '} + + Migrate to v1 + + +
+ ); +} diff --git a/content/Changelog/index.mdx b/content/Changelog/index.mdx deleted file mode 100644 index 811da137..00000000 --- a/content/Changelog/index.mdx +++ /dev/null @@ -1,12 +0,0 @@ ---- -title: Changelog -description: Release notes and version history for Agentuity products ---- - -This section contains the release notes and version history for Agentuity products: - -- [CLI](/Changelog/cli) - Command Line Interface [GitHub](https://github.com/agentuity/cli) -- [JavaScript SDK](/Changelog/sdk-js) - JavaScript/TypeScript SDK [GitHub](https://github.com/agentuity/sdk-js) -- [Python SDK](/Changelog/sdk-py) - Python SDK [GitHub](https://github.com/agentuity/sdk-py) - -Each page documents the changes, improvements, and bug fixes in each release. diff --git a/content/Cloud/index.mdx b/content/Cloud/index.mdx deleted file mode 100644 index 87e5803f..00000000 --- a/content/Cloud/index.mdx +++ /dev/null @@ -1,28 +0,0 @@ ---- -title: Overview -description: Introduction to the Agentuity Cloud Console and its features ---- - -The Agentuity Cloud Console is a web-based interface for managing your Agentuity resources, projects, and settings. This guide provides an overview of the main features and functionality available in the Cloud Console. - -- Create and manage projects via the [Project Dashboard](/Cloud/project) -- Configure settings and API keys [API Keys](/Cloud/api-keys) -- Manage agentic services ([Vector](/Cloud/vector-memory), [Key-Value](/Cloud/key-value-memory), and [AI Gateway](/Cloud/aigateway)) -- Monitor agent deployments and runs via the [Agent Dashboard](/Cloud/agents) -- View logs and telemetry data via the [Sessions](/Cloud/sessions) - -### Agentic Services - -The Services section provides tools for managing different types of agentic services: - -- **[Vector Memory Storage](/Cloud/vector-memory)**: For semantic search and large context windows, ideal for knowledge bases and historical data -- **[Key Value Storage](/Cloud/key-value-memory)**: For fast, simple, or temporary data storage or caching -- **[AI Gateway](/Cloud/aigateway)**: For managing access to various AI models and their usage and billing - -### Settings and Configuration - -The Settings section allows you to manage various configuration options: - -- **[API Keys](/Cloud/api-keys)**: Create and manage API keys for authenticating with Agentuity services -- **[Organization Settings](/Cloud/organization)**: Manage organization details and members -- **User Profile**: Update your user profile and preferences diff --git a/content/meta.json b/content/meta.json index ef46f8f7..e6fdd625 100644 --- a/content/meta.json +++ b/content/meta.json @@ -1,14 +1,4 @@ { "title": "Agentuity Docs", - "pages": [ - "Introduction", - "Guides", - "Cloud", - "CLI", - "SDKs", - "Changelog", - "Examples", - "Troubleshooting", - "v1" - ] + "pages": ["v1", "v0"] } diff --git a/content/CLI/agent.mdx b/content/v0/CLI/agent.mdx similarity index 100% rename from content/CLI/agent.mdx rename to content/v0/CLI/agent.mdx diff --git a/content/CLI/apikey.mdx b/content/v0/CLI/apikey.mdx similarity index 100% rename from content/CLI/apikey.mdx rename to content/v0/CLI/apikey.mdx diff --git a/content/CLI/auth.mdx b/content/v0/CLI/auth.mdx similarity index 100% rename from content/CLI/auth.mdx rename to content/v0/CLI/auth.mdx diff --git a/content/CLI/bundle.mdx b/content/v0/CLI/bundle.mdx similarity index 100% rename from content/CLI/bundle.mdx rename to content/v0/CLI/bundle.mdx diff --git a/content/CLI/cloud.mdx b/content/v0/CLI/cloud.mdx similarity index 100% rename from content/CLI/cloud.mdx rename to content/v0/CLI/cloud.mdx diff --git a/content/CLI/dev.mdx b/content/v0/CLI/dev.mdx similarity index 90% rename from content/CLI/dev.mdx rename to content/v0/CLI/dev.mdx index f7686b75..3309bb1b 100644 --- a/content/CLI/dev.mdx +++ b/content/v0/CLI/dev.mdx @@ -34,4 +34,4 @@ Run a development server for a project in a specific directory: ✓ Development server started -> See the [DevMode](/Guides/devmode) documentation for more information on how to use DevMode. \ No newline at end of file +> See the [DevMode](/v0/Guides/devmode) documentation for more information on how to use DevMode. \ No newline at end of file diff --git a/content/CLI/env.mdx b/content/v0/CLI/env.mdx similarity index 98% rename from content/CLI/env.mdx rename to content/v0/CLI/env.mdx index bbfe76c1..9043f85b 100644 --- a/content/CLI/env.mdx +++ b/content/v0/CLI/env.mdx @@ -120,4 +120,4 @@ Delete multiple environment variables: ✓ Environment variables and secrets deleted -> You can also manage your environment variables and secrets in the [Console](/Cloud/api-keys). \ No newline at end of file +> You can also manage your environment variables and secrets in the [Console](/v0/Cloud/api-keys). \ No newline at end of file diff --git a/content/CLI/installation.mdx b/content/v0/CLI/installation.mdx similarity index 99% rename from content/CLI/installation.mdx rename to content/v0/CLI/installation.mdx index 7a26a754..46571fa1 100644 --- a/content/CLI/installation.mdx +++ b/content/v0/CLI/installation.mdx @@ -40,7 +40,7 @@ To upgrade to the latest version of the Agentuity CLI: -For more details on the `version` command, see the [CLI Version Commands](/CLI/version). +For more details on the `version` command, see the [CLI Version Commands](/v0/CLI/version). ## Manual Installation diff --git a/content/CLI/mcp.mdx b/content/v0/CLI/mcp.mdx similarity index 100% rename from content/CLI/mcp.mdx rename to content/v0/CLI/mcp.mdx diff --git a/content/CLI/meta.json b/content/v0/CLI/meta.json similarity index 100% rename from content/CLI/meta.json rename to content/v0/CLI/meta.json diff --git a/content/CLI/project.mdx b/content/v0/CLI/project.mdx similarity index 100% rename from content/CLI/project.mdx rename to content/v0/CLI/project.mdx diff --git a/content/CLI/version.mdx b/content/v0/CLI/version.mdx similarity index 100% rename from content/CLI/version.mdx rename to content/v0/CLI/version.mdx diff --git a/content/Changelog/cli.mdx b/content/v0/Changelog/cli.mdx similarity index 100% rename from content/Changelog/cli.mdx rename to content/v0/Changelog/cli.mdx diff --git a/content/v0/Changelog/index.mdx b/content/v0/Changelog/index.mdx new file mode 100644 index 00000000..d04c7ee6 --- /dev/null +++ b/content/v0/Changelog/index.mdx @@ -0,0 +1,12 @@ +--- +title: Changelog +description: Release notes and version history for Agentuity products +--- + +This section contains the release notes and version history for Agentuity products: + +- [CLI](/v0/Changelog/cli) - Command Line Interface [GitHub](https://github.com/agentuity/cli) +- [JavaScript SDK](/v0/Changelog/sdk-js) - JavaScript/TypeScript SDK [GitHub](https://github.com/agentuity/sdk-js) +- [Python SDK](/v0/Changelog/sdk-py) - Python SDK [GitHub](https://github.com/agentuity/sdk-py) + +Each page documents the changes, improvements, and bug fixes in each release. diff --git a/content/Changelog/meta.json b/content/v0/Changelog/meta.json similarity index 100% rename from content/Changelog/meta.json rename to content/v0/Changelog/meta.json diff --git a/content/Changelog/sdk-js.mdx b/content/v0/Changelog/sdk-js.mdx similarity index 100% rename from content/Changelog/sdk-js.mdx rename to content/v0/Changelog/sdk-js.mdx diff --git a/content/Changelog/sdk-py.mdx b/content/v0/Changelog/sdk-py.mdx similarity index 100% rename from content/Changelog/sdk-py.mdx rename to content/v0/Changelog/sdk-py.mdx diff --git a/content/Cloud/agents.mdx b/content/v0/Cloud/agents.mdx similarity index 99% rename from content/Cloud/agents.mdx rename to content/v0/Cloud/agents.mdx index 5a7a5ea1..123df604 100644 --- a/content/Cloud/agents.mdx +++ b/content/v0/Cloud/agents.mdx @@ -60,7 +60,7 @@ graph TD You can think of the agent as an intelligent black box that receives data from sources and responds with data which is sent to the configured destinations. -Read the [Agent IO](/Guides/agent-io) guide for more information. +Read the [Agent IO](/v0/Guides/agent-io) guide for more information. You can add a new input or output by clicking the plus button in the agent IO visualization. @@ -466,4 +466,4 @@ You can use the CLI to manage your agents using the following command: -See the [CLI documentation](/CLI/agent) for more information on specific command usage and flags. +See the [CLI documentation](/v0/CLI/agent) for more information on specific command usage and flags. diff --git a/content/Cloud/aigateway.mdx b/content/v0/Cloud/aigateway.mdx similarity index 100% rename from content/Cloud/aigateway.mdx rename to content/v0/Cloud/aigateway.mdx diff --git a/content/Cloud/api-keys.mdx b/content/v0/Cloud/api-keys.mdx similarity index 93% rename from content/Cloud/api-keys.mdx rename to content/v0/Cloud/api-keys.mdx index ab10a01d..fb9d0815 100644 --- a/content/Cloud/api-keys.mdx +++ b/content/v0/Cloud/api-keys.mdx @@ -37,4 +37,4 @@ You can use the CLI to manage your API keys using the following command: -See the [CLI documentation](/CLI/apikey) for more information on specific command usage and flags. \ No newline at end of file +See the [CLI documentation](/v0/CLI/apikey) for more information on specific command usage and flags. \ No newline at end of file diff --git a/content/v0/Cloud/index.mdx b/content/v0/Cloud/index.mdx new file mode 100644 index 00000000..df68cd80 --- /dev/null +++ b/content/v0/Cloud/index.mdx @@ -0,0 +1,28 @@ +--- +title: Overview +description: Introduction to the Agentuity Cloud Console and its features +--- + +The Agentuity Cloud Console is a web-based interface for managing your Agentuity resources, projects, and settings. This guide provides an overview of the main features and functionality available in the Cloud Console. + +- Create and manage projects via the [Project Dashboard](/v0/Cloud/project) +- Configure settings and API keys [API Keys](/v0/Cloud/api-keys) +- Manage agentic services ([Vector](/v0/Cloud/vector-memory), [Key-Value](/v0/Cloud/key-value-memory), and [AI Gateway](/v0/Cloud/aigateway)) +- Monitor agent deployments and runs via the [Agent Dashboard](/v0/Cloud/agents) +- View logs and telemetry data via the [Sessions](/v0/Cloud/sessions) + +### Agentic Services + +The Services section provides tools for managing different types of agentic services: + +- **[Vector Memory Storage](/v0/Cloud/vector-memory)**: For semantic search and large context windows, ideal for knowledge bases and historical data +- **[Key Value Storage](/v0/Cloud/key-value-memory)**: For fast, simple, or temporary data storage or caching +- **[AI Gateway](/v0/Cloud/aigateway)**: For managing access to various AI models and their usage and billing + +### Settings and Configuration + +The Settings section allows you to manage various configuration options: + +- **[API Keys](/v0/Cloud/api-keys)**: Create and manage API keys for authenticating with Agentuity services +- **[Organization Settings](/v0/Cloud/organization)**: Manage organization details and members +- **User Profile**: Update your user profile and preferences diff --git a/content/Cloud/key-value-memory.mdx b/content/v0/Cloud/key-value-memory.mdx similarity index 100% rename from content/Cloud/key-value-memory.mdx rename to content/v0/Cloud/key-value-memory.mdx diff --git a/content/Cloud/meta.json b/content/v0/Cloud/meta.json similarity index 100% rename from content/Cloud/meta.json rename to content/v0/Cloud/meta.json diff --git a/content/Cloud/object-storage.mdx b/content/v0/Cloud/object-storage.mdx similarity index 100% rename from content/Cloud/object-storage.mdx rename to content/v0/Cloud/object-storage.mdx diff --git a/content/Cloud/organization.mdx b/content/v0/Cloud/organization.mdx similarity index 100% rename from content/Cloud/organization.mdx rename to content/v0/Cloud/organization.mdx diff --git a/content/Cloud/project.mdx b/content/v0/Cloud/project.mdx similarity index 96% rename from content/Cloud/project.mdx rename to content/v0/Cloud/project.mdx index 5f1c3c7c..d362540e 100644 --- a/content/Cloud/project.mdx +++ b/content/v0/Cloud/project.mdx @@ -58,4 +58,4 @@ You can use the CLI to manage your project using the following command: -See the [CLI documentation](/CLI/project) for more information on specific command usage and flags. \ No newline at end of file +See the [CLI documentation](/v0/CLI/project) for more information on specific command usage and flags. \ No newline at end of file diff --git a/content/Cloud/settings.mdx b/content/v0/Cloud/settings.mdx similarity index 100% rename from content/Cloud/settings.mdx rename to content/v0/Cloud/settings.mdx diff --git a/content/Cloud/vector-memory.mdx b/content/v0/Cloud/vector-memory.mdx similarity index 100% rename from content/Cloud/vector-memory.mdx rename to content/v0/Cloud/vector-memory.mdx diff --git a/content/Examples/index.mdx b/content/v0/Examples/index.mdx similarity index 100% rename from content/Examples/index.mdx rename to content/v0/Examples/index.mdx diff --git a/content/Guides/.agent-patterns.mdx b/content/v0/Guides/.agent-patterns.mdx similarity index 100% rename from content/Guides/.agent-patterns.mdx rename to content/v0/Guides/.agent-patterns.mdx diff --git a/content/Guides/.security.mdx b/content/v0/Guides/.security.mdx similarity index 100% rename from content/Guides/.security.mdx rename to content/v0/Guides/.security.mdx diff --git a/content/Guides/agent-communication.mdx b/content/v0/Guides/agent-communication.mdx similarity index 100% rename from content/Guides/agent-communication.mdx rename to content/v0/Guides/agent-communication.mdx diff --git a/content/Guides/agent-data-handling.mdx b/content/v0/Guides/agent-data-handling.mdx similarity index 99% rename from content/Guides/agent-data-handling.mdx rename to content/v0/Guides/agent-data-handling.mdx index de491e2f..1a874c21 100644 --- a/content/Guides/agent-data-handling.mdx +++ b/content/v0/Guides/agent-data-handling.mdx @@ -160,7 +160,7 @@ async def run(request: AgentRequest, response: AgentResponse, context: AgentCont You must await the `stream` method to get a stream of the raw binary data. The stream will be a `ReadableStream` object for JavaScript and a `IO[bytes]` object for Python. -See the [Streaming](/Guides/agent-streaming) guide for more information on how Agent Streaming works. +See the [Streaming](/v0/Guides/agent-streaming) guide for more information on how Agent Streaming works. ### Base64 diff --git a/content/Guides/agent-engineering.mdx b/content/v0/Guides/agent-engineering.mdx similarity index 97% rename from content/Guides/agent-engineering.mdx rename to content/v0/Guides/agent-engineering.mdx index fb3be917..2f47c5f9 100644 --- a/content/Guides/agent-engineering.mdx +++ b/content/v0/Guides/agent-engineering.mdx @@ -7,7 +7,7 @@ description: Effective software engineering in an agentic future Traditional software engineering is built around deterministic logic, tightly scoped inputs and outputs, and rigid control flows. When you start building agents, you're no longer programming every behavior—you're designing systems that can interpret goals, reason about context, and act autonomously. -> 🤔 Not sure what an agent is? Check out [What is an Agent?](/Guides/what-is-an-agent) +> 🤔 Not sure what an agent is? Check out [What is an Agent?](/v0/Guides/what-is-an-agent) Building agents isn't just about writing code—it's about shaping intelligent behavior. @@ -129,4 +129,4 @@ Modern AI can also not only return output, it can return structured output too ( Agents represent a new abstraction layer — between human goals and system actions. As an engineer, your job shifts from building every bridge to empowering an intelligent system to build those bridges for you. That's not just a technical challenge—it's a creative one. -Make sure you understand the differences between today's cloud computing paradigm and the [Agent-Native Cloud](/Guides/agent-native-cloud). \ No newline at end of file +Make sure you understand the differences between today's cloud computing paradigm and the [Agent-Native Cloud](/v0/Guides/agent-native-cloud). \ No newline at end of file diff --git a/content/Guides/agent-io.mdx b/content/v0/Guides/agent-io.mdx similarity index 97% rename from content/Guides/agent-io.mdx rename to content/v0/Guides/agent-io.mdx index 0f4497f6..17418490 100644 --- a/content/Guides/agent-io.mdx +++ b/content/v0/Guides/agent-io.mdx @@ -87,6 +87,6 @@ Required when your agent needs conversations AND notifications, connecting multi ## Next Steps -- Review the [Agents](/Cloud/agents) documentation for platform-specific configuration +- Review the [Agents](/v0/Cloud/agents) documentation for platform-specific configuration - Configure *sources* for platforms where users will interact with your agent - Set up *destinations* for platforms where your agent will send notifications diff --git a/content/Guides/agent-logging.mdx b/content/v0/Guides/agent-logging.mdx similarity index 98% rename from content/Guides/agent-logging.mdx rename to content/v0/Guides/agent-logging.mdx index fec1553b..a9e34b74 100644 --- a/content/Guides/agent-logging.mdx +++ b/content/v0/Guides/agent-logging.mdx @@ -250,4 +250,4 @@ context.logger.info(f"Payment processed order_id={order_id} amount={payment_amou context.logger.info(f"[ORDER] Validation completed order_id={order_id} item_count={item_count}") context.logger.info(f"[PAYMENT] Processing amount={payment_amount} method={payment_method}")`} /> -For more observability features, see [Agent Telemetry](/Guides/agent-telemetry) and [Agent Tracing](/Guides/agent-tracing). \ No newline at end of file +For more observability features, see [Agent Telemetry](/v0/Guides/agent-telemetry) and [Agent Tracing](/v0/Guides/agent-tracing). \ No newline at end of file diff --git a/content/Guides/agent-native-cloud.mdx b/content/v0/Guides/agent-native-cloud.mdx similarity index 99% rename from content/Guides/agent-native-cloud.mdx rename to content/v0/Guides/agent-native-cloud.mdx index db7a481c..f9c78bb8 100644 --- a/content/Guides/agent-native-cloud.mdx +++ b/content/v0/Guides/agent-native-cloud.mdx @@ -110,7 +110,7 @@ If your infrastructure can't help agents learn, adapt, and coordinate at machine • **Leadership** – Faster time-to-value, lower TCO, and infrastructure that *gets better with age*. -If you're in software engineering, you need to think like an [Agent Builder](/Guides/agent-engineering). +If you're in software engineering, you need to think like an [Agent Builder](/v0/Guides/agent-engineering). --- diff --git a/content/Guides/agent-streaming.mdx b/content/v0/Guides/agent-streaming.mdx similarity index 99% rename from content/Guides/agent-streaming.mdx rename to content/v0/Guides/agent-streaming.mdx index c518cd22..24c7d072 100644 --- a/content/Guides/agent-streaming.mdx +++ b/content/v0/Guides/agent-streaming.mdx @@ -515,6 +515,6 @@ const handler: AgentHandler = async (req, resp, ctx) => { ## Further Reading - Blog Post: [Agents just want to have streams](https://agentuity.com/blog/agent-streaming) -- SDK Examples: [JavaScript](/SDKs/javascript/examples#openai-streaming-example) · [Python](/SDKs/python/examples#streaming-responses-from-openai) +- SDK Examples: [JavaScript](/v0/SDKs/javascript/examples#openai-streaming-example) · [Python](/v0/SDKs/python/examples#streaming-responses-from-openai) - Streaming Video Demo: [Watch on YouTube](https://youtu.be/HN_ElBfsWtE) diff --git a/content/Guides/agent-telemetry.mdx b/content/v0/Guides/agent-telemetry.mdx similarity index 97% rename from content/Guides/agent-telemetry.mdx rename to content/v0/Guides/agent-telemetry.mdx index 86b8e3f2..ec588615 100644 --- a/content/Guides/agent-telemetry.mdx +++ b/content/v0/Guides/agent-telemetry.mdx @@ -69,4 +69,4 @@ Agentuity uses OpenTelemetry standards, enabling: - Correlation across distributed systems - Industry-standard tooling compatibility -For implementation details and code examples, see our SDK documentation for [JavaScript](/SDKs/javascript) and [Python](/SDKs/python). \ No newline at end of file +For implementation details and code examples, see our SDK documentation for [JavaScript](/v0/SDKs/javascript) and [Python](/v0/SDKs/python). \ No newline at end of file diff --git a/content/Guides/agent-tracing.mdx b/content/v0/Guides/agent-tracing.mdx similarity index 98% rename from content/Guides/agent-tracing.mdx rename to content/v0/Guides/agent-tracing.mdx index 5b42ff62..518228fb 100644 --- a/content/Guides/agent-tracing.mdx +++ b/content/v0/Guides/agent-tracing.mdx @@ -288,4 +288,4 @@ You can view traces in the session timeline visualization: -For more details on observability, see [Agent Logging](/Guides/agent-logging) for application logs and [Agent Telemetry](/Guides/agent-telemetry) for metrics and monitoring. +For more details on observability, see [Agent Logging](/v0/Guides/agent-logging) for application logs and [Agent Telemetry](/v0/Guides/agent-telemetry) for metrics and monitoring. diff --git a/content/Guides/ai-gateway.mdx b/content/v0/Guides/ai-gateway.mdx similarity index 97% rename from content/Guides/ai-gateway.mdx rename to content/v0/Guides/ai-gateway.mdx index 8d1cbbb8..910a292f 100644 --- a/content/Guides/ai-gateway.mdx +++ b/content/v0/Guides/ai-gateway.mdx @@ -122,7 +122,7 @@ The Agentuity CLI provides templates with these frameworks already configured. S ## Telemetry Integration -AI Gateway spans appear in your [agent telemetry](/Guides/agent-telemetry) with: +AI Gateway spans appear in your [agent telemetry](/v0/Guides/agent-telemetry) with: - Request duration - Token counts - Cost information diff --git a/content/Guides/devmode.mdx b/content/v0/Guides/devmode.mdx similarity index 97% rename from content/Guides/devmode.mdx rename to content/v0/Guides/devmode.mdx index 5c45b890..fea3f4cd 100644 --- a/content/Guides/devmode.mdx +++ b/content/v0/Guides/devmode.mdx @@ -117,7 +117,7 @@ Track your test runs with details on status, duration, costs, and performance me -DevMode sessions are temporary and cleared when you exit DevMode. For persistent production analytics, see [Agent Telemetry](/Guides/agent-telemetry). +DevMode sessions are temporary and cleared when you exit DevMode. For persistent production analytics, see [Agent Telemetry](/v0/Guides/agent-telemetry). ### Logs Tab @@ -127,7 +127,7 @@ Watch your agent's execution logs in real-time with structured timestamps, sever -DevMode logs are temporary and cleared when you exit DevMode. For persistent production logs, see [Agent Logging](/Guides/agent-logging). +DevMode logs are temporary and cleared when you exit DevMode. For persistent production logs, see [Agent Logging](/v0/Guides/agent-logging). ## Best Practices diff --git a/content/Guides/key-value.mdx b/content/v0/Guides/key-value.mdx similarity index 96% rename from content/Guides/key-value.mdx rename to content/v0/Guides/key-value.mdx index ed8ea58d..6f771bd1 100644 --- a/content/Guides/key-value.mdx +++ b/content/v0/Guides/key-value.mdx @@ -10,8 +10,8 @@ Key-value storage is your go-to solution for fast, ephemeral data that agents ne Choose the right storage for your use case: - **Key-Value**: Fast lookups, simple data, temporary state -- **[Vector Storage](/Cloud/vector-memory)**: Semantic search, embeddings, similarity matching -- **[Object Storage](/Cloud/object-storage)**: Large files, media, backups +- **[Vector Storage](/v0/Cloud/vector-memory)**: Semantic search, embeddings, similarity matching +- **[Object Storage](/v0/Cloud/object-storage)**: Large files, media, backups ## Common Use Cases @@ -197,7 +197,7 @@ Use TTL for temporary data to prevent storage bloat: ### Data Size Considerations -Key-value storage is optimized for small to medium-sized values. For large files or documents, consider using [Object Storage](/Cloud/object-storage) instead. +Key-value storage is optimized for small to medium-sized values. For large files or documents, consider using [Object Storage](/v0/Cloud/object-storage) instead. ## Monitoring Usage @@ -206,7 +206,7 @@ Track your key-value storage usage through the Cloud Console: 1. Navigate to **Services > Key Value** 2. View storage size and record count for each instance 3. Click on an instance to browse stored keys and values -4. Monitor [agent telemetry](/Guides/agent-telemetry) for KV operation performance +4. Monitor [agent telemetry](/v0/Guides/agent-telemetry) for KV operation performance diff --git a/content/Guides/meta.json b/content/v0/Guides/meta.json similarity index 100% rename from content/Guides/meta.json rename to content/v0/Guides/meta.json diff --git a/content/Guides/object-storage.mdx b/content/v0/Guides/object-storage.mdx similarity index 98% rename from content/Guides/object-storage.mdx rename to content/v0/Guides/object-storage.mdx index f4639c1e..c813b988 100644 --- a/content/Guides/object-storage.mdx +++ b/content/v0/Guides/object-storage.mdx @@ -10,8 +10,8 @@ Object storage is your solution for storing files, media, and large unstructured Choose the right storage for your use case: - **Object Storage**: Files, media, documents, backups -- **[Key-Value Storage](/Cloud/key-value-memory)**: Fast lookups, session data, configuration -- **[Vector Storage](/Cloud/vector-memory)**: Semantic search, embeddings, AI context +- **[Key-Value Storage](/v0/Cloud/key-value-memory)**: Fast lookups, session data, configuration +- **[Vector Storage](/v0/Cloud/vector-memory)**: Semantic search, embeddings, AI context ## Common Use Cases @@ -405,7 +405,7 @@ Track your object storage usage through the Cloud Console: 1. Navigate to **Services > Object Store** 2. View storage size and object count for each bucket 3. Monitor provider information and creation dates -4. Use [agent telemetry](/Guides/agent-telemetry) to track storage operations +4. Use [agent telemetry](/v0/Guides/agent-telemetry) to track storage operations For structured data with complex queries, consider using object storage to store data exports while maintaining indexes in key-value or vector storage. diff --git a/content/Guides/vector-db.mdx b/content/v0/Guides/vector-db.mdx similarity index 96% rename from content/Guides/vector-db.mdx rename to content/v0/Guides/vector-db.mdx index 4ff49d1f..6a972524 100644 --- a/content/Guides/vector-db.mdx +++ b/content/v0/Guides/vector-db.mdx @@ -10,8 +10,8 @@ Vector storage enables semantic search for your agents, allowing them to find in Choose the right storage for your use case: - **Vector Storage**: Semantic search, embeddings, similarity matching -- **[Key-Value Storage](/Cloud/key-value-memory)**: Fast lookups, simple data, temporary state -- **[Object Storage](/Cloud/object-storage)**: Large files, media, backups +- **[Key-Value Storage](/v0/Cloud/key-value-memory)**: Fast lookups, simple data, temporary state +- **[Object Storage](/v0/Cloud/object-storage)**: Large files, media, backups ## Understanding Vector Storage @@ -53,8 +53,8 @@ Vector storage is created automatically when your agent first calls `context.vec ## Vector Storage API For complete API documentation, see: -- [JavaScript SDK Vector API Reference](/SDKs/javascript/api-reference#vector-storage) -- [Python SDK Vector API Reference](/SDKs/python/api-reference#vector-storage) +- [JavaScript SDK Vector API Reference](/v0/SDKs/javascript/api-reference#vector-storage) +- [Python SDK Vector API Reference](/v0/SDKs/python/api-reference#vector-storage) ### Upserting Documents @@ -266,8 +266,8 @@ count = await context.vector.delete("knowledge-base", "doc_1") ## Practical Examples For more code examples, see: -- [JavaScript SDK Examples](/SDKs/javascript/examples#vector-storage-usage) -- [Python SDK Examples](/SDKs/python/examples#vector-storage) +- [JavaScript SDK Examples](/v0/SDKs/javascript/examples#vector-storage-usage) +- [Python SDK Examples](/v0/SDKs/python/examples#vector-storage) ### Building a Simple RAG System @@ -588,7 +588,7 @@ Vector storage serves as long-term memory for agents, enabling them to: - Retrieve relevant examples for few-shot learning - Build and maintain agent-specific knowledge repositories -For more information on memory patterns, see the [Key-Value Storage guide](/Guides/key-value) for short-term memory or explore [Agent Communication](/Guides/agent-communication) for sharing knowledge between agents. +For more information on memory patterns, see the [Key-Value Storage guide](/v0/Guides/key-value) for short-term memory or explore [Agent Communication](/v0/Guides/agent-communication) for sharing knowledge between agents. ## Storage Types Overview diff --git a/content/Guides/what-is-an-agent.mdx b/content/v0/Guides/what-is-an-agent.mdx similarity index 97% rename from content/Guides/what-is-an-agent.mdx rename to content/v0/Guides/what-is-an-agent.mdx index 61f2532e..a90c72e2 100644 --- a/content/Guides/what-is-an-agent.mdx +++ b/content/v0/Guides/what-is-an-agent.mdx @@ -70,9 +70,9 @@ We have optimized cloud computing largely by moving the compute to the edge, foc Agents demands a new paradigm. Agents aren't focused on low latency, they are focused on long-running, complex tasks that require a lot of context and coordination. They are not sessionless, they are stateful and both short and long term memory is required. They are not distributed, they are centralized — often close to the data resources or as close to low latency, high bandwith GPU clusters. -Learn more details about the differences in today's cloud computing paradigm and the [Agent-Native Cloud](/Guides/agent-native-cloud). +Learn more details about the differences in today's cloud computing paradigm and the [Agent-Native Cloud](/v0/Guides/agent-native-cloud). -If you're a software engineer and you need to build agents, you need to think like an [Agent Builder](/Guides/agent-engineering). +If you're a software engineer and you need to build agents, you need to think like an [Agent Builder](/v0/Guides/agent-engineering). ## Video Overview diff --git a/content/Introduction/architecture.mdx b/content/v0/Introduction/architecture.mdx similarity index 100% rename from content/Introduction/architecture.mdx rename to content/v0/Introduction/architecture.mdx diff --git a/content/Introduction/getting-started.mdx b/content/v0/Introduction/getting-started.mdx similarity index 94% rename from content/Introduction/getting-started.mdx rename to content/v0/Introduction/getting-started.mdx index ed760006..9743d271 100644 --- a/content/Introduction/getting-started.mdx +++ b/content/v0/Introduction/getting-started.mdx @@ -16,7 +16,7 @@ If you already have the Agentuity CLI installed, you can skip this step and sign ### Install the CLI -The [Agentuity CLI](/CLI/installation) is a cross-platform command-line tool for working with Agentuity Cloud. It supports Windows (using WSL), MacOS, and Linux. +The [Agentuity CLI](/v0/CLI/installation) is a cross-platform command-line tool for working with Agentuity Cloud. It supports Windows (using WSL), MacOS, and Linux. diff --git a/content/Introduction/index.mdx b/content/v0/Introduction/index.mdx similarity index 86% rename from content/Introduction/index.mdx rename to content/v0/Introduction/index.mdx index 8ff34b42..f515031e 100644 --- a/content/Introduction/index.mdx +++ b/content/v0/Introduction/index.mdx @@ -14,7 +14,7 @@ description: Agentuity is rebuilding the cloud for AI Agents Our mission is to provide a fully agentic infrastructure and tools necessary to build Agents that are fully operated by AI. -If you're ready to dive in, skip to [Getting Started](/Introduction/getting-started) to get your first Agent up and running in minutes. +If you're ready to dive in, skip to [Getting Started](/v0/Introduction/getting-started) to get your first Agent up and running in minutes. @@ -43,4 +43,4 @@ We see a near future where Agents are the primary way to build and operate softw allowFullScreen > -If you're ready to have your own Agent, [Get Started](/Introduction/getting-started) in just a few minutes. +If you're ready to have your own Agent, [Get Started](/v0/Introduction/getting-started) in just a few minutes. diff --git a/content/Introduction/kitchen-sink.mdx b/content/v0/Introduction/kitchen-sink.mdx similarity index 100% rename from content/Introduction/kitchen-sink.mdx rename to content/v0/Introduction/kitchen-sink.mdx diff --git a/content/Introduction/meta.json b/content/v0/Introduction/meta.json similarity index 100% rename from content/Introduction/meta.json rename to content/v0/Introduction/meta.json diff --git a/content/Introduction/templates.mdx b/content/v0/Introduction/templates.mdx similarity index 100% rename from content/Introduction/templates.mdx rename to content/v0/Introduction/templates.mdx diff --git a/content/SDKs/index.mdx b/content/v0/SDKs/index.mdx similarity index 100% rename from content/SDKs/index.mdx rename to content/v0/SDKs/index.mdx diff --git a/content/SDKs/javascript/api-reference.mdx b/content/v0/SDKs/javascript/api-reference.mdx similarity index 100% rename from content/SDKs/javascript/api-reference.mdx rename to content/v0/SDKs/javascript/api-reference.mdx diff --git a/content/SDKs/javascript/core-concepts.mdx b/content/v0/SDKs/javascript/core-concepts.mdx similarity index 100% rename from content/SDKs/javascript/core-concepts.mdx rename to content/v0/SDKs/javascript/core-concepts.mdx diff --git a/content/SDKs/javascript/error-handling.mdx b/content/v0/SDKs/javascript/error-handling.mdx similarity index 100% rename from content/SDKs/javascript/error-handling.mdx rename to content/v0/SDKs/javascript/error-handling.mdx diff --git a/content/SDKs/javascript/examples/index.mdx b/content/v0/SDKs/javascript/examples/index.mdx similarity index 100% rename from content/SDKs/javascript/examples/index.mdx rename to content/v0/SDKs/javascript/examples/index.mdx diff --git a/content/SDKs/javascript/examples/langchain.mdx b/content/v0/SDKs/javascript/examples/langchain.mdx similarity index 100% rename from content/SDKs/javascript/examples/langchain.mdx rename to content/v0/SDKs/javascript/examples/langchain.mdx diff --git a/content/SDKs/javascript/frameworks.mdx b/content/v0/SDKs/javascript/frameworks.mdx similarity index 100% rename from content/SDKs/javascript/frameworks.mdx rename to content/v0/SDKs/javascript/frameworks.mdx diff --git a/content/SDKs/javascript/index.mdx b/content/v0/SDKs/javascript/index.mdx similarity index 71% rename from content/SDKs/javascript/index.mdx rename to content/v0/SDKs/javascript/index.mdx index a9dbebcf..f03c0b4a 100644 --- a/content/SDKs/javascript/index.mdx +++ b/content/v0/SDKs/javascript/index.mdx @@ -6,7 +6,7 @@ description: Documentation for the Agentuity JavaScript SDK The [Agentuity JavaScript SDK](https://github.com/agentuity/sdk-js) provides a powerful framework for building AI agents in JavaScript and TypeScript. It offers a comprehensive set of tools for creating, deploying, and managing agents with features like key-value storage, vector storage, and OpenTelemetry integration. -**See it in action**: Deploy the [Kitchen Sink](/Introduction/kitchen-sink) to explore 20+ working agents that demonstrate every JavaScript SDK feature. Perfect for hands-on learning before building your own agents. +**See it in action**: Deploy the [Kitchen Sink](/v0/Introduction/kitchen-sink) to explore 20+ working agents that demonstrate every JavaScript SDK feature. Perfect for hands-on learning before building your own agents. ## Installation @@ -60,9 +60,9 @@ Make sure you replace `agent_ID` with the ID of your Agent. ## Next Steps -- [Core Concepts](/SDKs/javascript/core-concepts) - Learn about the fundamental concepts of the Agentuity JavaScript SDK -- [LLM](/SDKs/javascript/llm) - Learn about the LLM integrations available in the Agentuity JavaScript SDK -- [Frameworks](/SDKs/javascript/frameworks) - Learn about using frameworks with the Agentuity JavaScript SDK -- [API Reference](/SDKs/javascript/api-reference) - Explore the detailed API documentation -- [Error Handling](/SDKs/javascript/error-handling) - Learn about the error handling in the Agentuity JavaScript SDK -- [Examples](/SDKs/javascript/examples) - See practical examples of using the SDK +- [Core Concepts](/v0/SDKs/javascript/core-concepts) - Learn about the fundamental concepts of the Agentuity JavaScript SDK +- [LLM](/v0/SDKs/javascript/llm) - Learn about the LLM integrations available in the Agentuity JavaScript SDK +- [Frameworks](/v0/SDKs/javascript/frameworks) - Learn about using frameworks with the Agentuity JavaScript SDK +- [API Reference](/v0/SDKs/javascript/api-reference) - Explore the detailed API documentation +- [Error Handling](/v0/SDKs/javascript/error-handling) - Learn about the error handling in the Agentuity JavaScript SDK +- [Examples](/v0/SDKs/javascript/examples) - See practical examples of using the SDK diff --git a/content/SDKs/javascript/llm.mdx b/content/v0/SDKs/javascript/llm.mdx similarity index 100% rename from content/SDKs/javascript/llm.mdx rename to content/v0/SDKs/javascript/llm.mdx diff --git a/content/SDKs/javascript/meta.json b/content/v0/SDKs/javascript/meta.json similarity index 100% rename from content/SDKs/javascript/meta.json rename to content/v0/SDKs/javascript/meta.json diff --git a/content/SDKs/javascript/troubleshooting.mdx b/content/v0/SDKs/javascript/troubleshooting.mdx similarity index 100% rename from content/SDKs/javascript/troubleshooting.mdx rename to content/v0/SDKs/javascript/troubleshooting.mdx diff --git a/content/SDKs/meta.json b/content/v0/SDKs/meta.json similarity index 100% rename from content/SDKs/meta.json rename to content/v0/SDKs/meta.json diff --git a/content/SDKs/python/api-reference.mdx b/content/v0/SDKs/python/api-reference.mdx similarity index 100% rename from content/SDKs/python/api-reference.mdx rename to content/v0/SDKs/python/api-reference.mdx diff --git a/content/SDKs/python/async-api.mdx b/content/v0/SDKs/python/async-api.mdx similarity index 100% rename from content/SDKs/python/async-api.mdx rename to content/v0/SDKs/python/async-api.mdx diff --git a/content/SDKs/python/core-concepts.mdx b/content/v0/SDKs/python/core-concepts.mdx similarity index 100% rename from content/SDKs/python/core-concepts.mdx rename to content/v0/SDKs/python/core-concepts.mdx diff --git a/content/SDKs/python/data-handling.mdx b/content/v0/SDKs/python/data-handling.mdx similarity index 100% rename from content/SDKs/python/data-handling.mdx rename to content/v0/SDKs/python/data-handling.mdx diff --git a/content/SDKs/python/examples/index.mdx b/content/v0/SDKs/python/examples/index.mdx similarity index 100% rename from content/SDKs/python/examples/index.mdx rename to content/v0/SDKs/python/examples/index.mdx diff --git a/content/SDKs/python/examples/pydantic.mdx b/content/v0/SDKs/python/examples/pydantic.mdx similarity index 100% rename from content/SDKs/python/examples/pydantic.mdx rename to content/v0/SDKs/python/examples/pydantic.mdx diff --git a/content/SDKs/python/frameworks.mdx b/content/v0/SDKs/python/frameworks.mdx similarity index 100% rename from content/SDKs/python/frameworks.mdx rename to content/v0/SDKs/python/frameworks.mdx diff --git a/content/SDKs/python/index.mdx b/content/v0/SDKs/python/index.mdx similarity index 77% rename from content/SDKs/python/index.mdx rename to content/v0/SDKs/python/index.mdx index ed7cfe7e..06442e9a 100644 --- a/content/SDKs/python/index.mdx +++ b/content/v0/SDKs/python/index.mdx @@ -6,7 +6,7 @@ description: Documentation for the Agentuity Python SDK The [Agentuity Python SDK](https://github.com/agentuity/sdk-py) provides a powerful framework for building AI agents in Python. It offers a comprehensive set of tools for creating, deploying, and managing agents with features like key-value storage, vector storage, and OpenTelemetry integration. -**Coming soon**: A Python version of the [Kitchen Sink](/Introduction/kitchen-sink) is in development. For now, explore the TypeScript version to see what's possible with Agentuity's platform features. +**Coming soon**: A Python version of the [Kitchen Sink](/v0/Introduction/kitchen-sink) is in development. For now, explore the TypeScript version to see what's possible with Agentuity's platform features. ## Installation @@ -55,7 +55,7 @@ If you would like to manually test your Agents locally, you can do so by running ## Next Steps -- [Core Concepts](/SDKs/python/core-concepts) - Learn about the fundamental concepts of the Agentuity Python SDK -- [LLM](/SDKs/python/llm) - Learn about the LLM integrations available in the Agentuity Python SDK -- [Frameworks](/SDKs/python/frameworks) - Learn about using frameworks with the Agentuity Python SDK -- [API Reference](/SDKs/python/api-reference) - Explore the detailed API documentation +- [Core Concepts](/v0/SDKs/python/core-concepts) - Learn about the fundamental concepts of the Agentuity Python SDK +- [LLM](/v0/SDKs/python/llm) - Learn about the LLM integrations available in the Agentuity Python SDK +- [Frameworks](/v0/SDKs/python/frameworks) - Learn about using frameworks with the Agentuity Python SDK +- [API Reference](/v0/SDKs/python/api-reference) - Explore the detailed API documentation diff --git a/content/SDKs/python/llm.mdx b/content/v0/SDKs/python/llm.mdx similarity index 100% rename from content/SDKs/python/llm.mdx rename to content/v0/SDKs/python/llm.mdx diff --git a/content/SDKs/python/meta.json b/content/v0/SDKs/python/meta.json similarity index 100% rename from content/SDKs/python/meta.json rename to content/v0/SDKs/python/meta.json diff --git a/content/Troubleshooting/error-codes/authentication.mdx b/content/v0/Troubleshooting/error-codes/authentication.mdx similarity index 100% rename from content/Troubleshooting/error-codes/authentication.mdx rename to content/v0/Troubleshooting/error-codes/authentication.mdx diff --git a/content/Troubleshooting/error-codes/cli.mdx b/content/v0/Troubleshooting/error-codes/cli.mdx similarity index 100% rename from content/Troubleshooting/error-codes/cli.mdx rename to content/v0/Troubleshooting/error-codes/cli.mdx diff --git a/content/Troubleshooting/error-codes/datastores.mdx b/content/v0/Troubleshooting/error-codes/datastores.mdx similarity index 100% rename from content/Troubleshooting/error-codes/datastores.mdx rename to content/v0/Troubleshooting/error-codes/datastores.mdx diff --git a/content/Troubleshooting/error-codes/index.mdx b/content/v0/Troubleshooting/error-codes/index.mdx similarity index 100% rename from content/Troubleshooting/error-codes/index.mdx rename to content/v0/Troubleshooting/error-codes/index.mdx diff --git a/content/Troubleshooting/error-codes/integration.mdx b/content/v0/Troubleshooting/error-codes/integration.mdx similarity index 100% rename from content/Troubleshooting/error-codes/integration.mdx rename to content/v0/Troubleshooting/error-codes/integration.mdx diff --git a/content/Troubleshooting/error-codes/projects.mdx b/content/v0/Troubleshooting/error-codes/projects.mdx similarity index 100% rename from content/Troubleshooting/error-codes/projects.mdx rename to content/v0/Troubleshooting/error-codes/projects.mdx diff --git a/content/Troubleshooting/error-codes/system.mdx b/content/v0/Troubleshooting/error-codes/system.mdx similarity index 100% rename from content/Troubleshooting/error-codes/system.mdx rename to content/v0/Troubleshooting/error-codes/system.mdx diff --git a/content/v0/meta.json b/content/v0/meta.json new file mode 100644 index 00000000..1a739e5c --- /dev/null +++ b/content/v0/meta.json @@ -0,0 +1,15 @@ +{ + "title": "v0 (Legacy)", + "description": "Agentuity SDK 0.x", + "root": true, + "pages": [ + "Introduction", + "Guides", + "Cloud", + "CLI", + "SDKs", + "Changelog", + "Examples", + "Troubleshooting" + ] +} diff --git a/content/v1/Building/Agents/ai-gateway.mdx b/content/v1/Build/Agents/ai-gateway.mdx similarity index 93% rename from content/v1/Building/Agents/ai-gateway.mdx rename to content/v1/Build/Agents/ai-gateway.mdx index 4fdac380..3a123057 100644 --- a/content/v1/Building/Agents/ai-gateway.mdx +++ b/content/v1/Build/Agents/ai-gateway.mdx @@ -3,8 +3,6 @@ title: Using the AI Gateway description: Automatic LLM routing with observability and cost tracking --- -# Using the AI Gateway - Agentuity's AI Gateway routes LLM requests through a managed infrastructure, giving you unified observability and cost tracking across all model providers. ## How It Works @@ -174,6 +172,6 @@ We recommend using the AI Gateway for most projects. ## Next Steps -- [Using the AI SDK](/Building/Agents/ai-sdk-integration): Structured output, tool calling, and multi-turn conversations -- [Returning Streaming Responses](/Building/Agents/streaming-responses): Real-time chat UIs and progress indicators -- [Logging](/Building/Observability/logging): Debug requests and track LLM performance +- [Using the AI SDK](/Build/Agents/ai-sdk-integration): Structured output, tool calling, and multi-turn conversations +- [Returning Streaming Responses](/Build/Agents/streaming-responses): Real-time chat UIs and progress indicators +- [Logging](/Build/Observability/logging): Debug requests and track LLM performance diff --git a/content/v1/Building/Agents/ai-sdk-integration.mdx b/content/v1/Build/Agents/ai-sdk-integration.mdx similarity index 92% rename from content/v1/Building/Agents/ai-sdk-integration.mdx rename to content/v1/Build/Agents/ai-sdk-integration.mdx index 1efa4315..2d9d22ee 100644 --- a/content/v1/Building/Agents/ai-sdk-integration.mdx +++ b/content/v1/Build/Agents/ai-sdk-integration.mdx @@ -3,11 +3,9 @@ title: Using the AI SDK description: Generate text, structured data, and streams with the Vercel AI SDK --- -# Using the AI SDK - The [Vercel AI SDK](https://ai-sdk.dev) provides a consistent API for LLM interactions with built-in streaming, structured output, and tool calling. -Agentuity works with any approach—you can also use provider SDKs directly (Anthropic, OpenAI), or frameworks like Mastra and LangGraph. See [Using the AI Gateway](/Building/Agents/ai-gateway) for examples with different libraries. +Agentuity works with any approach—you can also use provider SDKs directly (Anthropic, OpenAI), or frameworks like Mastra and LangGraph. See [Using the AI Gateway](/Build/Agents/ai-gateway) for examples with different libraries. ## Installation @@ -186,7 +184,7 @@ const agent = createAgent({ export default agent; ``` -For detailed streaming patterns, see [Streaming Responses](/Building/Agents/streaming-responses). +For detailed streaming patterns, see [Streaming Responses](/Build/Agents/streaming-responses). ## Provider Configuration @@ -242,6 +240,6 @@ handler: async (ctx, input) => { ## Next Steps -- [Using the AI Gateway](/Building/Agents/ai-gateway): Observability, cost tracking, and provider switching -- [Returning Streaming Responses](/Building/Agents/streaming-responses): Chat UIs and long-form content generation -- [Evaluations](/Building/Agents/evaluations): Quality checks and output validation +- [Using the AI Gateway](/Build/Agents/ai-gateway): Observability, cost tracking, and provider switching +- [Returning Streaming Responses](/Build/Agents/streaming-responses): Chat UIs and long-form content generation +- [Evaluations](/Build/Agents/evaluations): Quality checks and output validation diff --git a/content/v1/Building/Agents/calling-other-agents.mdx b/content/v1/Build/Agents/calling-other-agents.mdx similarity index 97% rename from content/v1/Building/Agents/calling-other-agents.mdx rename to content/v1/Build/Agents/calling-other-agents.mdx index 0029577d..eefe813a 100644 --- a/content/v1/Building/Agents/calling-other-agents.mdx +++ b/content/v1/Build/Agents/calling-other-agents.mdx @@ -3,8 +3,6 @@ title: Calling Other Agents description: Build multi-agent systems with type-safe agent-to-agent communication --- -# Calling Other Agents - Break complex tasks into focused, reusable agents that communicate with type safety. Instead of building one large agent, create specialized agents that each handle a single responsibility. ## Basic Usage @@ -210,7 +208,7 @@ export default orchestrator; ``` -The orchestrator pattern is common in AI workflows where you want to separate concerns (generation, evaluation, formatting) into focused agents. You'll see this pattern in many of our [multi-agent examples](/Learning/Examples). +The orchestrator pattern is common in AI workflows where you want to separate concerns (generation, evaluation, formatting) into focused agents. You'll see this pattern in many of our [multi-agent examples](/Learn/Examples). ## Public Agents @@ -392,7 +390,7 @@ Focused agents are easier to test, reuse, and maintain. ### Use Schemas for Type Safety -Define schemas on all agents for type-safe communication. See [Creating Agents](/Building/Agents/creating-agents) to learn more about using schemas. +Define schemas on all agents for type-safe communication. See [Creating Agents](/Build/Agents/creating-agents) to learn more about using schemas. ```typescript // Source agent with output schema @@ -564,6 +562,6 @@ This example combines several patterns: ## Next Steps -- [Subagents](/Building/Agents/subagents): Organize related agents into parent-child hierarchies -- [State Management](/Building/Agents/state-management): Share data across agent calls with thread and session state -- [Evaluations](/Building/Agents/evaluations): Add quality checks to your agent workflows +- [Subagents](/Build/Agents/subagents): Organize related agents into parent-child hierarchies +- [State Management](/Build/Agents/state-management): Share data across agent calls with thread and session state +- [Evaluations](/Build/Agents/evaluations): Add quality checks to your agent workflows diff --git a/content/v1/Building/Agents/creating-agents.mdx b/content/v1/Build/Agents/creating-agents.mdx similarity index 93% rename from content/v1/Building/Agents/creating-agents.mdx rename to content/v1/Build/Agents/creating-agents.mdx index 14bea6e1..ae717e90 100644 --- a/content/v1/Building/Agents/creating-agents.mdx +++ b/content/v1/Build/Agents/creating-agents.mdx @@ -3,8 +3,6 @@ title: Creating Agents description: Build agents with createAgent(), schemas, and handlers --- -# Creating Agents - Each agent encapsulates a handler function, input/output validation, and metadata in a single unit that can be invoked from routes, other agents, or scheduled tasks. ## Basic Agent @@ -67,7 +65,7 @@ export default agent; - Invalid data throws an error with details about what failed -Agentuity supports any library implementing StandardSchema, including [Valibot](https://valibot.dev) and [ArkType](https://arktype.io). See [Schema Libraries](/Building/Agents/schema-libraries) for examples with alternative libraries. +Agentuity supports any library implementing StandardSchema, including [Valibot](https://valibot.dev) and [ArkType](https://arktype.io). See [Schema Libraries](/Build/Agents/schema-libraries) for examples with alternative libraries. ### Type Inference @@ -181,7 +179,7 @@ handler: async (ctx, input) => { } ``` -For detailed state management patterns, see [Managing State](/Building/Agents/state-management). +For detailed state management patterns, see [Managing State](/Build/Agents/state-management). ## Agent Metadata @@ -250,6 +248,6 @@ handler: async (ctx, input) => { ## Next Steps -- [Using the AI SDK](/Building/Agents/ai-sdk-integration): Add LLM capabilities with generateText and streamText -- [Managing State](/Building/Agents/state-management): Persist data across requests with thread and session state -- [Calling Other Agents](/Building/Agents/calling-other-agents): Build multi-agent workflows +- [Using the AI SDK](/Build/Agents/ai-sdk-integration): Add LLM capabilities with generateText and streamText +- [Managing State](/Build/Agents/state-management): Persist data across requests with thread and session state +- [Calling Other Agents](/Build/Agents/calling-other-agents): Build multi-agent workflows diff --git a/content/v1/Building/Agents/evaluations.mdx b/content/v1/Build/Agents/evaluations.mdx similarity index 95% rename from content/v1/Building/Agents/evaluations.mdx rename to content/v1/Build/Agents/evaluations.mdx index dd8756ce..cb502810 100644 --- a/content/v1/Building/Agents/evaluations.mdx +++ b/content/v1/Build/Agents/evaluations.mdx @@ -3,8 +3,6 @@ title: Evaluations description: Automatically test and validate agent outputs for quality and compliance --- -# Evaluations - Evaluations (evals) are automated tests that run after your agent completes. They validate output quality, check compliance, and monitor performance without blocking agent responses. Evals come in two types: **binary** (pass/fail) for yes/no criteria, and **score** (0-1) for quality gradients. @@ -213,7 +211,7 @@ Data stored in `ctx.state` during agent execution persists to eval handlers. Use
-Built-in evals (`agent.createEval()`) are designed for automated background monitoring of a single agent's output. For user-facing comparison reports or cross-model evaluations (like comparing GPT vs Claude outputs), use custom agent chaining with `generateObject` instead. See [Learning/Examples](/Learning/Examples) for advanced patterns. +Built-in evals (`agent.createEval()`) are designed for automated background monitoring of a single agent's output. For user-facing comparison reports or cross-model evaluations (like comparing GPT vs Claude outputs), use custom agent chaining with `generateObject` instead. See [Learning/Examples](/Learn/Examples) for advanced patterns. ## Multiple Evals @@ -303,6 +301,6 @@ Eval errors are logged but don't affect agent responses. ## Next Steps -- [Events & Lifecycle](/Building/Agents/events-lifecycle): Monitor agent execution with lifecycle hooks -- [State Management](/Building/Agents/state-management): Share data between handlers and evals -- [Calling Other Agents](/Building/Agents/calling-other-agents): Build multi-agent workflows +- [Events & Lifecycle](/Build/Agents/events-lifecycle): Monitor agent execution with lifecycle hooks +- [State Management](/Build/Agents/state-management): Share data between handlers and evals +- [Calling Other Agents](/Build/Agents/calling-other-agents): Build multi-agent workflows diff --git a/content/v1/Building/Agents/events-lifecycle.mdx b/content/v1/Build/Agents/events-lifecycle.mdx similarity index 91% rename from content/v1/Building/Agents/events-lifecycle.mdx rename to content/v1/Build/Agents/events-lifecycle.mdx index f26c0f50..8937b209 100644 --- a/content/v1/Building/Agents/events-lifecycle.mdx +++ b/content/v1/Build/Agents/events-lifecycle.mdx @@ -3,8 +3,6 @@ title: Events & Lifecycle description: Lifecycle hooks for monitoring and extending agent behavior --- -# Events & Lifecycle - Events provide lifecycle hooks for monitoring agent execution. Use them for logging, metrics, analytics, and error tracking. ## Agent Events @@ -152,10 +150,10 @@ agent.addEventListener('completed', (event, agent, ctx) => { | **Blocking** | Synchronous | Background (`waitUntil`) | | **Output** | Logs, metrics | Pass/fail, scores | -Use events for observability. Use [evaluations](/Building/Agents/evaluations) for output quality checks. +Use events for observability. Use [evaluations](/Build/Agents/evaluations) for output quality checks. ## Next Steps -- [Evaluations](/Building/Agents/evaluations): Automated quality testing for agent outputs -- [State Management](/Building/Agents/state-management): Thread and session state patterns -- [Calling Other Agents](/Building/Agents/calling-other-agents): Multi-agent coordination +- [Evaluations](/Build/Agents/evaluations): Automated quality testing for agent outputs +- [State Management](/Build/Agents/state-management): Thread and session state patterns +- [Calling Other Agents](/Build/Agents/calling-other-agents): Multi-agent coordination diff --git a/content/v1/Building/Agents/meta.json b/content/v1/Build/Agents/meta.json similarity index 100% rename from content/v1/Building/Agents/meta.json rename to content/v1/Build/Agents/meta.json diff --git a/content/v1/Building/Agents/schema-libraries.mdx b/content/v1/Build/Agents/schema-libraries.mdx similarity index 96% rename from content/v1/Building/Agents/schema-libraries.mdx rename to content/v1/Build/Agents/schema-libraries.mdx index 97cca4d6..0563a6b4 100644 --- a/content/v1/Building/Agents/schema-libraries.mdx +++ b/content/v1/Build/Agents/schema-libraries.mdx @@ -3,8 +3,6 @@ title: Schema Libraries description: Using Valibot and ArkType as alternatives to Zod --- -# Schema Libraries - Agentuity uses the [StandardSchema](https://github.com/standard-schema/standard-schema) interface for validation, which means you can use any compatible library. While we recommend Zod for most projects, Valibot and ArkType are solid alternatives with different trade-offs. ## Choosing a Library @@ -198,5 +196,5 @@ createAgent({ schema, handler: async (ctx, input) => { ... } }); ## Next Steps -- [Creating Agents](/Building/Agents/creating-agents): Full guide to agent creation with Zod examples -- [Using the AI SDK](/Building/Agents/ai-sdk-integration): Add LLM capabilities to your agents +- [Creating Agents](/Build/Agents/creating-agents): Full guide to agent creation with Zod examples +- [Using the AI SDK](/Build/Agents/ai-sdk-integration): Add LLM capabilities to your agents diff --git a/content/v1/Building/Agents/state-management.mdx b/content/v1/Build/Agents/state-management.mdx similarity index 95% rename from content/v1/Building/Agents/state-management.mdx rename to content/v1/Build/Agents/state-management.mdx index 6e7097e3..f9e71ae2 100644 --- a/content/v1/Building/Agents/state-management.mdx +++ b/content/v1/Build/Agents/state-management.mdx @@ -3,8 +3,6 @@ title: Managing State description: Request, thread, and session state for stateful agents --- -# Managing State - Agentuity provides three state scopes for managing data across requests: - request state for temporary calculations @@ -189,7 +187,7 @@ handler: async (ctx, input) => { } ``` -**Note:** Session state is in-memory and cleared on server restart. For durable data, use [KV storage](/Building/Storage/key-value). +**Note:** Session state is in-memory and cleared on server restart. For durable data, use [KV storage](/Build/Storage/key-value). ## Persisting to Storage @@ -308,7 +306,7 @@ ctx.session.addEventListener('completed', async (eventName, session) => { }); ``` -For app-level event monitoring, see [Events & Lifecycle](/Building/Agents/events-lifecycle). +For app-level event monitoring, see [Events & Lifecycle](/Build/Agents/events-lifecycle). ## Thread vs Session IDs @@ -350,6 +348,6 @@ if (messages.length > 50) { ## Next Steps -- [Key-Value Storage](/Building/Storage/key-value): Durable data persistence with namespaces and TTL -- [Calling Other Agents](/Building/Agents/calling-other-agents): Share state between agents in workflows -- [Events & Lifecycle](/Building/Agents/events-lifecycle): Monitor agent execution and cleanup +- [Key-Value Storage](/Build/Storage/key-value): Durable data persistence with namespaces and TTL +- [Calling Other Agents](/Build/Agents/calling-other-agents): Share state between agents in workflows +- [Events & Lifecycle](/Build/Agents/events-lifecycle): Monitor agent execution and cleanup diff --git a/content/v1/Building/Agents/streaming-responses.mdx b/content/v1/Build/Agents/streaming-responses.mdx similarity index 92% rename from content/v1/Building/Agents/streaming-responses.mdx rename to content/v1/Build/Agents/streaming-responses.mdx index 8d862692..b0275466 100644 --- a/content/v1/Building/Agents/streaming-responses.mdx +++ b/content/v1/Build/Agents/streaming-responses.mdx @@ -3,8 +3,6 @@ title: Returning Streaming Responses description: Return real-time LLM output with streaming agents --- -# Returning Streaming Responses - Show LLM output as it's generated instead of waiting for the full response. Streaming reduces perceived latency and creates a more responsive experience. ## Streaming Types @@ -35,7 +33,7 @@ await stream.write('data'); await stream.close(); ``` -This page focuses on ephemeral streaming with the AI SDK. For persistent streaming patterns, see the [Storage documentation](/Building/Storage/durable-streams). +This page focuses on ephemeral streaming with the AI SDK. For persistent streaming patterns, see the [Storage documentation](/Build/Storage/durable-streams). Streaming requires both: `schema.stream: true` in your agent (so the handler returns a stream) and `router.stream()` in your route (so the response is streamed to the client). @@ -140,7 +138,7 @@ function Chat() { } ``` -For streaming with React, see [Frontend Hooks](/Building/Frontend/react-hooks). +For streaming with React, see [Frontend Hooks](/Build/Frontend/react-hooks). ## Streaming with System Prompts @@ -221,6 +219,6 @@ Errors in streaming are part of the stream, not thrown exceptions. Always provid ## Next Steps -- [Using the AI SDK](/Building/Agents/ai-sdk-integration): Structured output and non-streaming responses -- [State Management](/Building/Agents/state-management): Multi-turn conversations with memory -- [Server-Sent Events](/Building/Routes-Triggers/sse): Server-push updates without polling +- [Using the AI SDK](/Build/Agents/ai-sdk-integration): Structured output and non-streaming responses +- [State Management](/Build/Agents/state-management): Multi-turn conversations with memory +- [Server-Sent Events](/Build/Routes-Triggers/sse): Server-push updates without polling diff --git a/content/v1/Building/Agents/subagents.mdx b/content/v1/Build/Agents/subagents.mdx similarity index 97% rename from content/v1/Building/Agents/subagents.mdx rename to content/v1/Build/Agents/subagents.mdx index 8371a6e9..90a70c69 100644 --- a/content/v1/Building/Agents/subagents.mdx +++ b/content/v1/Build/Agents/subagents.mdx @@ -3,8 +3,6 @@ title: Subagents description: Organize related agents into parent-child hierarchies --- -# Subagents - Group related agents under a parent to create hierarchical organizations. Subagents share context with their parent, inherit route paths, and can access their parent via `ctx.parent`. ## When to Use Subagents @@ -420,6 +418,6 @@ const child = createAgent({ ## Next Steps -- [Calling Other Agents](/Building/Agents/calling-other-agents): Communication patterns between agents -- [HTTP Routes](/Building/Routes-Triggers/http-routes): Complete routing patterns and middleware -- [State Management](/Building/Agents/state-management): Share data between parent and subagents +- [Calling Other Agents](/Build/Agents/calling-other-agents): Communication patterns between agents +- [HTTP Routes](/Build/Routes-Triggers/http-routes): Complete routing patterns and middleware +- [State Management](/Build/Agents/state-management): Share data between parent and subagents diff --git a/content/v1/Building/Routes-Triggers/cron.mdx b/content/v1/Build/Routes-Triggers/cron.mdx similarity index 78% rename from content/v1/Building/Routes-Triggers/cron.mdx rename to content/v1/Build/Routes-Triggers/cron.mdx index ac5b46e5..debf46b6 100644 --- a/content/v1/Building/Routes-Triggers/cron.mdx +++ b/content/v1/Build/Routes-Triggers/cron.mdx @@ -5,6 +5,8 @@ description: Run tasks on a schedule with router.cron() Schedule recurring tasks using cron expressions. Schedules are defined in code, deployed with your agents, and automatically provisioned. +TODO: what does "automatically provisioned" mean here? is that the right wording? + ## Basic Example ```typescript @@ -12,7 +14,7 @@ import { createRouter } from '@agentuity/runtime'; const router = createRouter(); -router.cron('0 9 * * *', async (c) => { +router.cron('0 9 * * *', async (c) => { // runs daily at 9am c.var.logger.info('Daily report starting'); const report = await c.agent.reportGenerator.run({ @@ -44,13 +46,13 @@ Standard five-field cron format: ## Common Schedules -| Schedule | Expression | Description | +| Schedule | Expression | Useful for | |----------|------------|-------------| | Every 5 minutes | `*/5 * * * *` | Health checks, quick sync | -| Hourly | `0 * * * *` | Aggregations, cleanup | -| Daily at 9am | `0 9 * * *` | Reports, notifications | -| Weekly (Sunday midnight) | `0 0 * * 0` | Weekly summaries | -| Monthly (1st at midnight) | `0 0 1 * *` | Monthly reports | +| Hourly | `0 * * * *` | Aggregations, cleanup, etc. | +| Daily at 9am | `0 9 * * *` | Reports, notifications, etc. | +| Weekly (Sunday midnight) | `0 0 * * 0` | Weekly summaries, etc. | +| Monthly (1st at midnight) | `0 0 1 * *` | Monthly reports, etc. | ## Full Example @@ -61,7 +63,7 @@ import { createRouter } from '@agentuity/runtime'; const router = createRouter(); -router.cron('*/5 * * * *', async (c) => { +router.cron('*/5 * * * *', async (c) => { // runs every 5 minutes const startTime = Date.now(); c.var.logger.info('Health check starting'); @@ -114,6 +116,6 @@ async function cronHandler(c) { ## Next Steps -- [HTTP Routes](/Building/Routes-Triggers/http-routes) — Standard request/response endpoints -- [Email Handling](/Building/Routes-Triggers/email) — Process incoming emails -- [Key-Value Storage](/Building/Storage/key-value) — Store job results +- [HTTP Routes](/Build/Routes-Triggers/http-routes): Standard request/response endpoints +- [Email Handling](/Build/Routes-Triggers/email): Process incoming emails +- [Key-Value Storage](/Build/Storage/key-value): Store job results diff --git a/content/v1/Building/Routes-Triggers/email.mdx b/content/v1/Build/Routes-Triggers/email.mdx similarity index 93% rename from content/v1/Building/Routes-Triggers/email.mdx rename to content/v1/Build/Routes-Triggers/email.mdx index 73d0cbeb..a38cc048 100644 --- a/content/v1/Building/Routes-Triggers/email.mdx +++ b/content/v1/Build/Routes-Triggers/email.mdx @@ -124,6 +124,6 @@ The `email.sendReply()` method for sending replies is coming soon. Currently, yo ## Next Steps -- [SMS Handling](/Building/Routes-Triggers/sms) — Process incoming text messages -- [Scheduled Jobs](/Building/Routes-Triggers/cron) — Run tasks on a schedule -- [Key-Value Storage](/Building/Storage/key-value) — Store processed email data +- [SMS Handling](/Build/Routes-Triggers/sms): Process incoming text messages +- [Scheduled Jobs](/Build/Routes-Triggers/cron): Run tasks on a schedule +- [Key-Value Storage](/Build/Storage/key-value): Store processed email data diff --git a/content/v1/Building/Routes-Triggers/http-routes.mdx b/content/v1/Build/Routes-Triggers/http-routes.mdx similarity index 88% rename from content/v1/Building/Routes-Triggers/http-routes.mdx rename to content/v1/Build/Routes-Triggers/http-routes.mdx index 33f74cad..9a762d72 100644 --- a/content/v1/Building/Routes-Triggers/http-routes.mdx +++ b/content/v1/Build/Routes-Triggers/http-routes.mdx @@ -6,7 +6,7 @@ description: Define GET, POST, and other HTTP endpoints with createRouter() Routes define how your application responds to HTTP requests. Built on [Hono](https://hono.dev), the router provides a familiar Express-like API with full TypeScript support. -Routes are defined in your codebase (`route.ts` files) and automatically discovered by the SDK. Changes are tracked in git, reviewed in pull requests, and deployed with your code. No UI configuration required. +Routes are defined in your codebase (`route.ts` files) and automatically discovered by the SDK. Changes are tracked in Git, reviewed in pull requests, and deployed with your code. No UI configuration required. ## Basic Routes @@ -35,10 +35,10 @@ export default router; The router supports all standard HTTP methods: ```typescript -router.get('/items', handler); // Read -router.post('/items', handler); // Create -router.put('/items/:id', handler); // Replace -router.patch('/items/:id', handler); // Update +router.get('/items', handler); // Read +router.post('/items', handler); // Create +router.put('/items/:id', handler); // Replace +router.patch('/items/:id', handler); // Update router.delete('/items/:id', handler); // Delete ``` @@ -200,7 +200,7 @@ router.get('/users/me', getCurrentUser); // Never reached ## Next Steps -- [Scheduled Jobs (Cron)](/Building/Routes-Triggers/cron) — Run tasks on a schedule -- [Email Handling](/Building/Routes-Triggers/email) — Process incoming emails -- [WebSockets](/Building/Routes-Triggers/websockets) — Real-time bidirectional communication -- [Server-Sent Events](/Building/Routes-Triggers/sse) — Stream updates to clients +- [Scheduled Jobs (Cron)](/Build/Routes-Triggers/cron): Run tasks on a schedule +- [Email Handling](/Build/Routes-Triggers/email): Process incoming emails +- [WebSockets](/Build/Routes-Triggers/websockets): Real-time bidirectional communication +- [Server-Sent Events](/Build/Routes-Triggers/sse): Stream updates to clients diff --git a/content/v1/Building/Routes-Triggers/meta.json b/content/v1/Build/Routes-Triggers/meta.json similarity index 100% rename from content/v1/Building/Routes-Triggers/meta.json rename to content/v1/Build/Routes-Triggers/meta.json diff --git a/content/v1/Building/Routes-Triggers/sms.mdx b/content/v1/Build/Routes-Triggers/sms.mdx similarity index 82% rename from content/v1/Building/Routes-Triggers/sms.mdx rename to content/v1/Build/Routes-Triggers/sms.mdx index 526a8bd9..f477df49 100644 --- a/content/v1/Building/Routes-Triggers/sms.mdx +++ b/content/v1/Build/Routes-Triggers/sms.mdx @@ -12,7 +12,7 @@ import { createRouter } from '@agentuity/runtime'; const router = createRouter(); -router.sms({ number: '+15551234567' }, async (c) => { +router.sms({ number: '+1234567890' }, async (c) => { // Parse Twilio payload const formData = await c.req.parseBody(); const from = formData['From'] as string; @@ -35,7 +35,7 @@ export default router; Twilio sends webhooks in form-encoded format by default, but can also send JSON: ```typescript -router.sms({ number: '+15551234567' }, async (c) => { +router.sms({ number: '+1234567890' }, async (c) => { let from = 'unknown'; let body = ''; @@ -67,7 +67,7 @@ import { createRouter } from '@agentuity/runtime'; const router = createRouter(); -router.sms({ number: '+15551234567' }, async (c) => { +router.sms({ number: '+1234567890' }, async (c) => { try { const formData = await c.req.parseBody(); const from = formData['From'] as string; @@ -103,13 +103,7 @@ export default router; ## Phone Number Format -Phone numbers must use E.164 international format: - -| Format | Example | -|--------|---------| -| US | `+15551234567` | -| UK | `+447911123456` | -| Germany | `+4915112345678` | +Phone numbers must use E.164 international format: `+[country code][number]` with no spaces or dashes (e.g., `+1234567890`). ## How Replies Work @@ -122,6 +116,6 @@ return c.text('Thanks for your message!'); ## Next Steps -- [Email Handling](/Building/Routes-Triggers/email) — Process incoming emails -- [WebSockets](/Building/Routes-Triggers/websockets) — Real-time bidirectional communication -- [HTTP Routes](/Building/Routes-Triggers/http-routes) — Standard API endpoints +- [Email Handling](/Build/Routes-Triggers/email): Process incoming emails +- [WebSockets](/Build/Routes-Triggers/websockets): Real-time bidirectional communication +- [HTTP Routes](/Build/Routes-Triggers/http-routes): Standard API endpoints diff --git a/content/v1/Building/Routes-Triggers/sse.mdx b/content/v1/Build/Routes-Triggers/sse.mdx similarity index 80% rename from content/v1/Building/Routes-Triggers/sse.mdx rename to content/v1/Build/Routes-Triggers/sse.mdx index 17ca8787..0d9d1ce2 100644 --- a/content/v1/Building/Routes-Triggers/sse.mdx +++ b/content/v1/Build/Routes-Triggers/sse.mdx @@ -3,11 +3,7 @@ title: Server-Sent Events (SSE) description: Stream updates from server to client with router.sse() --- - -Server-Sent Events support is new in the v1 SDK, providing efficient one-way streaming from server to client. - - -Stream real-time updates to clients over a persistent HTTP connection. Ideal for progress indicators, live feeds, and LLM response streaming. +Server-Sent Events (SSE) provide efficient one-way streaming from server to client over HTTP. Use them for progress indicators, live feeds, notifications, and LLM response streaming. ## Basic Example @@ -37,11 +33,11 @@ The SSE handler uses an async callback pattern: ```typescript router.sse('/path', (c) => async (stream) => { - // c - Hono context (available in closure) + // c - Route context (logger, agents, storage) // stream - SSE stream object - await stream.write('data'); // Simple write - await stream.writeSSE({ event, data, id }); // Full SSE format + await stream.write('data'); + await stream.writeSSE({ event, data, id }); stream.onAbort(() => { /* cleanup */ }); stream.close(); }); @@ -62,9 +58,9 @@ Automatically formats data as SSE. ```typescript await stream.writeSSE({ - event: 'status', // Event type for client filtering - data: 'Processing...', // The payload - id: '1', // Optional event ID + event: 'status', // Event type for client filtering + data: 'Processing...', // The payload + id: '1', // Optional event ID }); ``` @@ -209,10 +205,10 @@ curl -N https://your-project.agentuity.cloud/agent-name | Browser support | Native EventSource | Native WebSocket | | Best for | Progress, feeds, LLM streaming | Chat, collaboration | -Use SSE when you only need to push data **from server to client**. Use [WebSockets](/Building/Routes-Triggers/websockets) when you need **bidirectional** communication. +Use SSE when you only need to push data **from server to client**. Use [WebSockets](/Build/Routes-Triggers/websockets) when you need **bidirectional** communication. ## Next Steps -- [WebSockets](/Building/Routes-Triggers/websockets) — Bidirectional real-time communication -- [HTTP Routes](/Building/Routes-Triggers/http-routes) — Standard request/response endpoints -- [React Hooks](/Building/Frontend/react-hooks) — Connect from React with `useAgentEventStream` +- [WebSockets](/Build/Routes-Triggers/websockets): Bidirectional real-time communication +- [HTTP Routes](/Build/Routes-Triggers/http-routes): Standard request/response endpoints +- [React Hooks](/Build/Frontend/react-hooks): Connect from React with `useAgentEventStream` diff --git a/content/v1/Building/Routes-Triggers/websockets.mdx b/content/v1/Build/Routes-Triggers/websockets.mdx similarity index 84% rename from content/v1/Building/Routes-Triggers/websockets.mdx rename to content/v1/Build/Routes-Triggers/websockets.mdx index 53ec257a..3d01a167 100644 --- a/content/v1/Building/Routes-Triggers/websockets.mdx +++ b/content/v1/Build/Routes-Triggers/websockets.mdx @@ -3,11 +3,7 @@ title: WebSockets description: Real-time bidirectional communication with router.websocket() --- - -WebSocket support is new in the v1 SDK, enabling real-time bidirectional communication for chat interfaces, live dashboards, and collaborative tools. - - -Create persistent connections where both client and server can send messages at any time. +WebSockets enable persistent, bidirectional connections between client and server. Use them for chat interfaces, live dashboards, collaborative tools, and any scenario requiring real-time two-way communication. ## Basic Example @@ -53,8 +49,8 @@ router.websocket('/path', (c) => (ws) => { ## WebSocket Events -| Event | Trigger | Use Case | -|-------|---------|----------| +| Event | Trigger | Example Use Case | +|-------|---------|------------------| | `onOpen` | Connection established | Send welcome message, initialize state | | `onMessage` | Client sends data | Process messages, call agents | | `onClose` | Connection ends | Clean up resources | @@ -172,10 +168,10 @@ ws.onclose = () => { | Progress updates | | ✓ | | | Request/response API | | | ✓ | -Use WebSockets when you need **bidirectional** communication. For **server-to-client only** streaming, consider [Server-Sent Events](/Building/Routes-Triggers/sse). +Use WebSockets when you need **bidirectional** communication. For **server-to-client only** streaming, consider [Server-Sent Events](/Build/Routes-Triggers/sse). ## Next Steps -- [Server-Sent Events](/Building/Routes-Triggers/sse) — One-way streaming from server to client -- [HTTP Routes](/Building/Routes-Triggers/http-routes) — Standard request/response endpoints -- [React Hooks](/Building/Frontend/react-hooks) — Connect from React with `useAgentWebsocket` +- [Server-Sent Events](/Build/Routes-Triggers/sse): One-way streaming from server to client +- [HTTP Routes](/Build/Routes-Triggers/http-routes): Standard request/response endpoints +- [React Hooks](/Build/Frontend/react-hooks): Connect from React with `useAgentWebsocket` diff --git a/content/v1/Building/meta.json b/content/v1/Build/meta.json similarity index 66% rename from content/v1/Building/meta.json rename to content/v1/Build/meta.json index 3a0fc682..03c0f0f8 100644 --- a/content/v1/Building/meta.json +++ b/content/v1/Build/meta.json @@ -1,4 +1,4 @@ { - "title": "Building", + "title": "Build", "pages": ["Agents", "Routes-Triggers"] } diff --git a/content/v1/Getting-Started/app-configuration.mdx b/content/v1/Get-Started/app-configuration.mdx similarity index 92% rename from content/v1/Getting-Started/app-configuration.mdx rename to content/v1/Get-Started/app-configuration.mdx index 3f843274..3a91c371 100644 --- a/content/v1/Getting-Started/app-configuration.mdx +++ b/content/v1/Get-Started/app-configuration.mdx @@ -117,6 +117,6 @@ This approach means: ## Next Steps -- [HTTP Routes](/Building/Routes-Triggers/http-routes) — Define HTTP endpoints -- [Cron Jobs](/Building/Routes-Triggers/cron) — Schedule recurring tasks -- [AI Gateway](/Building/Agents/ai-gateway) — Configure LLM providers +- [HTTP Routes](/Build/Routes-Triggers/http-routes) — Define HTTP endpoints +- [Cron Jobs](/Build/Routes-Triggers/cron) — Schedule recurring tasks +- [AI Gateway](/Build/Agents/ai-gateway) — Configure LLM providers diff --git a/content/v1/Getting-Started/installation.mdx b/content/v1/Get-Started/installation.mdx similarity index 84% rename from content/v1/Getting-Started/installation.mdx rename to content/v1/Get-Started/installation.mdx index 3486a52f..d8a9bf64 100644 --- a/content/v1/Getting-Started/installation.mdx +++ b/content/v1/Get-Started/installation.mdx @@ -44,5 +44,5 @@ The dev server includes hot reload, so changes to your agents and routes are ref ## Next Steps -- [Quickstart](/Getting-Started/quickstart) — Build your first agent -- [Project Structure](/Getting-Started/project-structure) — Understand the file layout +- [Quickstart](/Get-Started/quickstart) — Build your first agent +- [Project Structure](/Get-Started/project-structure) — Understand the file layout diff --git a/content/v1/Getting-Started/meta.json b/content/v1/Get-Started/meta.json similarity index 80% rename from content/v1/Getting-Started/meta.json rename to content/v1/Get-Started/meta.json index d5e69715..67c02f2b 100644 --- a/content/v1/Getting-Started/meta.json +++ b/content/v1/Get-Started/meta.json @@ -1,5 +1,5 @@ { - "title": "Getting Started", + "title": "Get Started", "pages": [ "what-is-agentuity", "installation", diff --git a/content/v1/Getting-Started/project-structure.mdx b/content/v1/Get-Started/project-structure.mdx similarity index 92% rename from content/v1/Getting-Started/project-structure.mdx rename to content/v1/Get-Started/project-structure.mdx index 7a942bf4..c20c44ae 100644 --- a/content/v1/Getting-Started/project-structure.mdx +++ b/content/v1/Get-Started/project-structure.mdx @@ -114,6 +114,6 @@ You can also deploy your frontend elsewhere (e.g.,Vercel, Netlify) and call your ## Next Steps -- [App Configuration](/Getting-Started/app-configuration) — Configure `app.ts` and `agentuity.json` -- [Creating Agents](/Building/Agents/creating-agents) — Deep dive into agent creation -- [HTTP Routes](/Building/Routes-Triggers/http-routes) — Learn about routing options +- [App Configuration](/Get-Started/app-configuration) — Configure `app.ts` and `agentuity.json` +- [Creating Agents](/Build/Agents/creating-agents) — Deep dive into agent creation +- [HTTP Routes](/Build/Routes-Triggers/http-routes) — Learn about routing options diff --git a/content/v1/Getting-Started/quickstart.mdx b/content/v1/Get-Started/quickstart.mdx similarity index 85% rename from content/v1/Getting-Started/quickstart.mdx rename to content/v1/Get-Started/quickstart.mdx index ac7b1710..2c568e41 100644 --- a/content/v1/Getting-Started/quickstart.mdx +++ b/content/v1/Get-Started/quickstart.mdx @@ -134,11 +134,11 @@ Your agent is now live with a public URL. **Build something more:** -- [Build a multi-agent system](/Building/Agents/calling-other-agents) — Routing, RAG, workflows -- [Add a React frontend](/Building/Frontend/react-hooks) — Call your agent from the web -- [Persist data](/Building/Agents/state-management) — Use thread and session state +- [Build a multi-agent system](/Build/Agents/calling-other-agents) — Routing, RAG, workflows +- [Add a React frontend](/Build/Frontend/react-hooks) — Call your agent from the web +- [Persist data](/Build/Agents/state-management) — Use thread and session state **Understand the platform:** -- [Project Structure](/Getting-Started/project-structure) — The two-file pattern -- [App Configuration](/Getting-Started/app-configuration) — Configure your project +- [Project Structure](/Get-Started/project-structure) — The two-file pattern +- [App Configuration](/Get-Started/app-configuration) — Configure your project diff --git a/content/v1/Getting-Started/what-is-agentuity.mdx b/content/v1/Get-Started/what-is-agentuity.mdx similarity index 88% rename from content/v1/Getting-Started/what-is-agentuity.mdx rename to content/v1/Get-Started/what-is-agentuity.mdx index 220a2ff7..75998fc7 100644 --- a/content/v1/Getting-Started/what-is-agentuity.mdx +++ b/content/v1/Get-Started/what-is-agentuity.mdx @@ -41,7 +41,7 @@ This agent uses the AI SDK to call OpenAI and respond to messages. Deploy with ` - **Routes & Triggers** — HTTP, WebSocket, SSE, cron, email, SMS - **Storage** — Key-value, vector, object storage, durable streams - **Observability** — Logging, tracing, real-time analytics -- **[AI Gateway](/Building/Agents/ai-gateway)** — Route LLM calls through OpenAI, Anthropic, Google, and more +- **[AI Gateway](/Build/Agents/ai-gateway)** — Route LLM calls through OpenAI, Anthropic, Google, and more - **Evaluations** — Automated quality checks that run after each agent execution - **Frontend** — Deploy React apps alongside your agents @@ -62,5 +62,5 @@ Infrastructure like cron schedules and email handlers are defined in code, not c ## Next Steps -- [Installation](/Getting-Started/installation) — Set up your environment -- [Quickstart](/Getting-Started/quickstart) — Build your first agent in 5 minutes +- [Installation](/Get-Started/installation) — Set up your environment +- [Quickstart](/Get-Started/quickstart) — Build your first agent in 5 minutes diff --git a/content/v1/meta.json b/content/v1/meta.json index a8822f36..80509b72 100644 --- a/content/v1/meta.json +++ b/content/v1/meta.json @@ -1,7 +1,9 @@ { - "title": "v1 Documentation", + "title": "v1 (Latest)", + "description": "Agentuity SDK 1.x", + "root": true, "pages": [ - "Getting-Started", - "Building" + "Get-Started", + "Build" ] } \ No newline at end of file diff --git a/content/v1/migration-guide.mdx b/content/v1/migration-guide.mdx index 76f9a1f7..67f21772 100644 --- a/content/v1/migration-guide.mdx +++ b/content/v1/migration-guide.mdx @@ -3,8 +3,6 @@ title: Migrating from v0 to v1 description: A comprehensive guide to migrating your Agentuity agents from v0 to v1 --- -# Migrating from v0 to v1 - Agentuity v1 represents a significant evolution of the platform, introducing new capabilities for building AI agents with improved type safety, better developer experience, and powerful new features like evaluations and advanced routing. This guide will help you migrate your existing v0 agents to v1, understand the breaking changes, and take advantage of the new features. diff --git a/next.config.mjs b/next.config.mjs index 3e9bc8f7..89783726 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -11,12 +11,53 @@ const config = { redirects: async () => [ { source: '/', - destination: '/Introduction', - permanent: true, + destination: '/v0/Introduction', + permanent: false, }, { source: '/docs', - destination: '/', + destination: '/v0/Introduction', + permanent: true, + }, + // Redirect old v0 paths to new /v0/ prefix + { + source: '/Introduction/:path*', + destination: '/v0/Introduction/:path*', + permanent: true, + }, + { + source: '/Guides/:path*', + destination: '/v0/Guides/:path*', + permanent: true, + }, + { + source: '/Cloud/:path*', + destination: '/v0/Cloud/:path*', + permanent: true, + }, + { + source: '/CLI/:path*', + destination: '/v0/CLI/:path*', + permanent: true, + }, + { + source: '/SDKs/:path*', + destination: '/v0/SDKs/:path*', + permanent: true, + }, + { + source: '/Changelog/:path*', + destination: '/v0/Changelog/:path*', + permanent: true, + }, + { + source: '/Examples/:path*', + destination: '/v0/Examples/:path*', + permanent: true, + }, + { + source: '/Troubleshooting/:path*', + destination: '/v0/Troubleshooting/:path*', permanent: true, }, ], From f0b4cb4bfa289a75ee3acdc2bfa4223b2fc447a6 Mon Sep 17 00:00:00 2001 From: parteeksingh24 Date: Wed, 26 Nov 2025 16:34:57 -0800 Subject: [PATCH 13/63] Update version tabs, fix sidebar spacing --- app/global.css | 9 +++++++++ content/v0/meta.json | 2 +- content/v1/meta.json | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/app/global.css b/app/global.css index b379f21b..d35afe6b 100644 --- a/app/global.css +++ b/app/global.css @@ -201,3 +201,12 @@ figure > div:nth-child(2) { article > p { margin-bottom: 1rem; } + +/* Remove spacing when no banner follows (v1 only) */ +#nd-sidebar > div:first-child:has(> button:last-child) { + padding-bottom: 0; +} + +#nd-sidebar > div:first-child > button:last-child { + margin-bottom: 0; +} diff --git a/content/v0/meta.json b/content/v0/meta.json index 1a739e5c..be7db651 100644 --- a/content/v0/meta.json +++ b/content/v0/meta.json @@ -1,5 +1,5 @@ { - "title": "v0 (Legacy)", + "title": "Legacy", "description": "Agentuity SDK 0.x", "root": true, "pages": [ diff --git a/content/v1/meta.json b/content/v1/meta.json index 80509b72..fc19e28f 100644 --- a/content/v1/meta.json +++ b/content/v1/meta.json @@ -1,5 +1,5 @@ { - "title": "v1 (Latest)", + "title": "Latest (v1)", "description": "Agentuity SDK 1.x", "root": true, "pages": [ From 659426f3e0eaaea08194c9d4662a32af51427196 Mon Sep 17 00:00:00 2001 From: parteeksingh24 Date: Wed, 26 Nov 2025 16:48:35 -0800 Subject: [PATCH 14/63] Reorder Agents docs --- content/v1/Build/Agents/meta.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/content/v1/Build/Agents/meta.json b/content/v1/Build/Agents/meta.json index 58b3529b..314d7da0 100644 --- a/content/v1/Build/Agents/meta.json +++ b/content/v1/Build/Agents/meta.json @@ -2,8 +2,8 @@ "title": "Agents", "pages": [ "creating-agents", - "ai-sdk-integration", "ai-gateway", + "ai-sdk-integration", "streaming-responses", "state-management", "schema-libraries", From 763a727811e2389be67e373a35117432a1fabb75 Mon Sep 17 00:00:00 2001 From: Parteek Singh Date: Mon, 1 Dec 2025 11:28:43 -0800 Subject: [PATCH 15/63] Set v0 as current, update CLI commands - Flip version picker: v0 is "Current", v1 is "Preview" - Replace deprecation banner with preview banner - Update install/quickstart to use agentuity CLI - Remove TODOs and archived content --- app/(docs)/layout.tsx | 4 +- components/V0DeprecationBanner.tsx | 28 - components/V1PreviewBanner.tsx | 19 + content/meta.json | 2 +- content/v0/meta.json | 2 +- content/v1/Build/Routes-Triggers/cron.mdx | 2 - content/v1/Examples/index.mdx | 1 - content/v1/Get-Started/installation.mdx | 27 +- content/v1/Get-Started/quickstart.mdx | 2 +- content/v1/Get-Started/what-is-agentuity.mdx | 2 + content/v1/Introduction/core-concepts.mdx | 1 - content/v1/archive/agent-communication.mdx | 859 ----------------- content/v1/archive/architecture.mdx | 546 ----------- content/v1/archive/context-types.mdx | 181 ---- content/v1/archive/evaluations.mdx | 919 ------------------- content/v1/archive/events.mdx | 685 -------------- content/v1/archive/introduction.mdx | 45 - content/v1/archive/routing-triggers.mdx | 492 ---------- content/v1/archive/schema-validation.mdx | 711 -------------- content/v1/archive/sessions-threads.mdx | 880 ------------------ content/v1/archive/subagents-old.mdx | 747 --------------- content/v1/meta.json | 2 +- content/v1/migration-guide.mdx | 4 +- 23 files changed, 38 insertions(+), 6123 deletions(-) delete mode 100644 components/V0DeprecationBanner.tsx create mode 100644 components/V1PreviewBanner.tsx delete mode 100644 content/v1/archive/agent-communication.mdx delete mode 100644 content/v1/archive/architecture.mdx delete mode 100644 content/v1/archive/context-types.mdx delete mode 100644 content/v1/archive/evaluations.mdx delete mode 100644 content/v1/archive/events.mdx delete mode 100644 content/v1/archive/introduction.mdx delete mode 100644 content/v1/archive/routing-triggers.mdx delete mode 100644 content/v1/archive/schema-validation.mdx delete mode 100644 content/v1/archive/sessions-threads.mdx delete mode 100644 content/v1/archive/subagents-old.mdx diff --git a/app/(docs)/layout.tsx b/app/(docs)/layout.tsx index 4ab92a8d..51088e25 100644 --- a/app/(docs)/layout.tsx +++ b/app/(docs)/layout.tsx @@ -4,7 +4,7 @@ import type { ReactNode } from 'react'; import { baseOptions } from '@/app/layout.config'; import { source } from '@/lib/source'; import AISearchToggle from '../../components/AISearchToggle'; -import { V0DeprecationBanner } from '../../components/V0DeprecationBanner'; +import { V1PreviewBanner } from '../../components/V1PreviewBanner'; export default function Layout({ children }: { children: ReactNode }) { return ( @@ -22,7 +22,7 @@ export default function Layout({ children }: { children: ReactNode }) { }, }} sidebar={{ - banner: , + banner: , }} > {children} diff --git a/components/V0DeprecationBanner.tsx b/components/V0DeprecationBanner.tsx deleted file mode 100644 index 4b82c56b..00000000 --- a/components/V0DeprecationBanner.tsx +++ /dev/null @@ -1,28 +0,0 @@ -'use client'; - -import { usePathname } from 'next/navigation'; -import Link from 'next/link'; -import { AlertTriangle } from 'lucide-react'; - -export function V0DeprecationBanner() { - const pathname = usePathname(); - - if (!pathname?.startsWith('/v0')) { - return null; - } - - return ( -
- - - v0 is deprecated.{' '} - - Migrate to v1 - - -
- ); -} diff --git a/components/V1PreviewBanner.tsx b/components/V1PreviewBanner.tsx new file mode 100644 index 00000000..e29ed1b7 --- /dev/null +++ b/components/V1PreviewBanner.tsx @@ -0,0 +1,19 @@ +'use client'; + +import { usePathname } from 'next/navigation'; +import { Info } from 'lucide-react'; + +export function V1PreviewBanner() { + const pathname = usePathname(); + + if (!pathname?.startsWith('/v1')) { + return null; + } + + return ( +
+ + v1 is in preview. Feedback welcome! +
+ ); +} diff --git a/content/meta.json b/content/meta.json index e6fdd625..bddc3146 100644 --- a/content/meta.json +++ b/content/meta.json @@ -1,4 +1,4 @@ { "title": "Agentuity Docs", - "pages": ["v1", "v0"] + "pages": ["v0", "v1"] } diff --git a/content/v0/meta.json b/content/v0/meta.json index be7db651..ab68e6d8 100644 --- a/content/v0/meta.json +++ b/content/v0/meta.json @@ -1,5 +1,5 @@ { - "title": "Legacy", + "title": "Current", "description": "Agentuity SDK 0.x", "root": true, "pages": [ diff --git a/content/v1/Build/Routes-Triggers/cron.mdx b/content/v1/Build/Routes-Triggers/cron.mdx index debf46b6..b97844a2 100644 --- a/content/v1/Build/Routes-Triggers/cron.mdx +++ b/content/v1/Build/Routes-Triggers/cron.mdx @@ -5,8 +5,6 @@ description: Run tasks on a schedule with router.cron() Schedule recurring tasks using cron expressions. Schedules are defined in code, deployed with your agents, and automatically provisioned. -TODO: what does "automatically provisioned" mean here? is that the right wording? - ## Basic Example ```typescript diff --git a/content/v1/Examples/index.mdx b/content/v1/Examples/index.mdx index 3a1bd134..26e09787 100644 --- a/content/v1/Examples/index.mdx +++ b/content/v1/Examples/index.mdx @@ -1409,4 +1409,3 @@ export default agent; **Learn More:** - [Agents just want to have streams](https://agentuity.com/blog/agent-streaming) -- TODO: add a streams demo for v1 diff --git a/content/v1/Get-Started/installation.mdx b/content/v1/Get-Started/installation.mdx index d8a9bf64..5d631568 100644 --- a/content/v1/Get-Started/installation.mdx +++ b/content/v1/Get-Started/installation.mdx @@ -3,29 +3,22 @@ title: Installation description: Set up your development environment --- -## Prerequisites - -- **Node.js 18+** or **Bun** (recommended) -- A code editor - ## Install the CLI ```bash -npm install -g @agentuity/cli +curl -fsS https://agentuity.sh | sh ``` -Or with Bun (recommended): -TODO: not sure our stuff works with anything BUT Bun...need to double check this, but I think we're "tied to Bun" as my CEO said recently. +This installs the `agentuity` CLI globally on your system. -```bash -bun install -g @agentuity/cli -``` + +If you prefer package managers: `bun install -g @agentuity/cli` + ## Create a Project ```bash -agentuity new my-project -cd my-project +agentuity create ``` The CLI will prompt you to select a template and configure your project. @@ -33,13 +26,13 @@ The CLI will prompt you to select a template and configure your project. ## Start the Dev Server ```bash -bun dev +agentuity dev ``` -Your project is now running at `http://localhost:3500`. +Your project is now running at `http://localhost:3500`. The dev server includes hot reload, so changes to your agents and routes are reflected immediately. - -The dev server includes hot reload, so changes to your agents and routes are reflected immediately. + +Agentuity projects run on [Bun](https://bun.sh). If you don't have Bun installed, the CLI will prompt you to install it when you create your first project. ## Next Steps diff --git a/content/v1/Get-Started/quickstart.mdx b/content/v1/Get-Started/quickstart.mdx index 2c568e41..e590a567 100644 --- a/content/v1/Get-Started/quickstart.mdx +++ b/content/v1/Get-Started/quickstart.mdx @@ -58,7 +58,7 @@ export default router; Start the dev server: ```bash -bun dev +agentuity dev ``` Send a request: diff --git a/content/v1/Get-Started/what-is-agentuity.mdx b/content/v1/Get-Started/what-is-agentuity.mdx index 75998fc7..41602084 100644 --- a/content/v1/Get-Started/what-is-agentuity.mdx +++ b/content/v1/Get-Started/what-is-agentuity.mdx @@ -11,6 +11,8 @@ Agentuity is a full-stack platform for building, deploying, and operating AI age Write TypeScript, define routes, and deploy with a single command. We handle the infrastructure, from automatic scaling to built-in observability and more. +Here's a basic example to get you started: + ```typescript import { createAgent } from '@agentuity/runtime'; import { generateText } from 'ai'; diff --git a/content/v1/Introduction/core-concepts.mdx b/content/v1/Introduction/core-concepts.mdx index 4bacb2d9..461deefe 100644 --- a/content/v1/Introduction/core-concepts.mdx +++ b/content/v1/Introduction/core-concepts.mdx @@ -309,7 +309,6 @@ See the [Evaluations Guide](/SDKs/javascript/evaluations) for complete documenta Now that you understand the core concepts, you can: -- TODO: Create dedicated deployment guide - Explore the [Routing & Triggers Guide](/Guides/routing-triggers) for WebSocket, SSE, and specialized routes - Learn about [Schema Validation](/SDKs/javascript/schema-validation) in depth - Set up [Evaluations](/SDKs/javascript/evaluations) to test your agents diff --git a/content/v1/archive/agent-communication.mdx b/content/v1/archive/agent-communication.mdx deleted file mode 100644 index b372d8d7..00000000 --- a/content/v1/archive/agent-communication.mdx +++ /dev/null @@ -1,859 +0,0 @@ ---- -title: Agent Communication -description: Learn how to build multi-agent systems with effective communication patterns ---- - -# Agent Communication - -TODO: no handoff, inter-project/org feature in v1? - -Build complex agentic systems by creating specialized agents that communicate and collaborate to achieve goals. - -## Overview - -In advanced agentic systems, agents work together by delegating tasks, sharing data, and coordinating workflows. The recommended approach is to build agents with highly specialized roles and use agent-to-agent communication to achieve the overall goal. - -For example, instead of building one large "customer support" agent, you might build: -- A **routing agent** that classifies the request -- A **knowledge agent** that searches documentation -- A **response agent** that generates customer-friendly answers -- A **sentiment agent** that analyzes customer tone - -Each agent focuses on one task and communicates with others as needed. - -## Basic Agent Calling - -Agents call other agents using the context object's agent registry. The SDK provides type-safe access to all agents in your project. - -### Calling an Agent - -```typescript -import { createAgent } from '@agentuity/runtime'; -import { z } from 'zod'; - -const coordinatorAgent = createAgent({ - schema: { - input: z.object({ text: z.string() }), - output: z.object({ result: z.string() }) - }, - handler: async (ctx, input) => { - // Call another agent - const enriched = await ctx.agent.enrichmentAgent.run({ - text: input.text - }); - - // Use the result - return { result: enriched.enrichedText }; - } -}); -``` - -When both agents have schemas, the call is fully type-safe. TypeScript validates the input type and infers the output type automatically. - -For detailed information about the agent calling API, see [Core Concepts](/SDK/core-concepts#agent-communication). - -## Communication Patterns - -Different workflows require different communication patterns. This section covers the most common patterns for coordinating multiple agents. - -### Sequential Workflows - -Sequential execution processes data through a series of agents, where each agent depends on the output of the previous agent. - -```typescript -const pipelineAgent = createAgent({ - handler: async (ctx, input) => { - // Step 1: Validate input - const validated = await ctx.agent.validatorAgent.run({ - data: input.rawData - }); - - // Step 2: Enrich with additional data - const enriched = await ctx.agent.enrichmentAgent.run({ - data: validated.cleanData - }); - - // Step 3: Analyze the enriched data - const analyzed = await ctx.agent.analysisAgent.run({ - data: enriched.enrichedData - }); - - return analyzed; - } -}); -``` - -**When to use:** -- Data transformation pipelines -- Multi-step validation or processing -- Workflows where each step depends on the previous result - -**Error handling:** -Errors propagate automatically. If `validatorAgent` throws an error, `enrichmentAgent` and `analysisAgent` never execute. - -### Parallel Execution - -Parallel execution runs multiple agents simultaneously when their operations are independent. - -```typescript -const searchAgent = createAgent({ - handler: async (ctx, input) => { - // Execute all searches in parallel - const [webResults, dbResults, vectorResults] = await Promise.all([ - ctx.agent.webSearchAgent.run({ query: input.query }), - ctx.agent.databaseAgent.run({ query: input.query }), - ctx.agent.vectorSearchAgent.run({ query: input.query }) - ]); - - // Merge and rank results - return { - results: mergeResults(webResults, dbResults, vectorResults) - }; - } -}); -``` - -**When to use:** -- Independent data fetching operations -- Multiple validation checks -- Gathering data from different sources -- Operations that can run concurrently - -**Performance benefit:** -If each agent takes 1 second, sequential execution takes 3 seconds, but parallel execution takes only 1 second. - -### Conditional Routing - -Use an LLM to classify user intent and route to the appropriate agent: - -```typescript -import { groq } from '@ai-sdk/groq'; -import { generateObject } from 'ai'; -import { z } from 'zod'; - -const IntentSchema = z.object({ - agentType: z.enum(['support', 'sales', 'technical', 'billing']), - confidence: z.number().min(0).max(1), - reasoning: z.string() -}); - -const routerAgent = createAgent({ - schema: { - input: z.object({ userMessage: z.string() }) - }, - handler: async (ctx, input) => { - // Classify intent with Groq (fast inference via AI Gateway) - const intent = await generateObject({ - model: groq('llama-3.3-70b'), // Groq (fast inference via AI Gateway) for quick classification - schema: IntentSchema, - system: 'You are a request classifier. Analyze the user message and determine which agent should handle it. Return the agent type, confidence score, and brief reasoning.', - prompt: input.userMessage, - temperature: 0.0 // Deterministic output for routing decisions - }); - - ctx.logger.info('Intent classified', { - type: intent.object.agentType, - confidence: intent.object.confidence - }); - - // Route based on classified intent - switch (intent.object.agentType) { - case 'support': - return await ctx.agent.supportAgent.run({ - message: input.userMessage, - context: intent.object.reasoning - }); - - case 'sales': - return await ctx.agent.salesAgent.run({ - message: input.userMessage, - context: intent.object.reasoning - }); - - case 'technical': - return await ctx.agent.technicalAgent.run({ - message: input.userMessage, - context: intent.object.reasoning - }); - - case 'billing': - return await ctx.agent.billingAgent.run({ - message: input.userMessage, - context: intent.object.reasoning - }); - } - } -}); -``` - -**When to use:** -- Natural language user inputs -- Multiple potential routing paths -- Need to understand nuanced intent -- Adaptive routing based on context - -## Advanced Workflows - -### Multi-Stage Pipelines - -Build complex pipelines with checkpoints and conditional branching: - -```typescript -const contentModerationAgent = createAgent({ - handler: async (ctx, input) => { - // Stage 1: Initial screening - const screening = await ctx.agent.screeningAgent.run({ - content: input.text - }); - - if (!screening.passed) { - return { - approved: false, - reason: 'Failed initial screening', - flags: screening.flags - }; - } - - // Stage 2: Detailed analysis (parallel) - const [sentiment, toxicity, pii] = await Promise.all([ - ctx.agent.sentimentAgent.run({ text: input.text }), - ctx.agent.toxicityAgent.run({ text: input.text }), - ctx.agent.piiDetectionAgent.run({ text: input.text }) - ]); - - // Stage 3: Final decision - const decision = await ctx.agent.decisionAgent.run({ - screening, - sentiment, - toxicity, - pii - }); - - return decision; - } -}); -``` - -This pattern combines sequential and parallel execution with conditional logic for sophisticated workflows. - -### Fan-Out/Fan-In - -Distribute work to multiple agents and aggregate the results: - -```typescript -const documentAnalysisAgent = createAgent({ - schema: { - input: z.object({ - document: z.string(), - sections: z.array(z.string()) - }) - }, - handler: async (ctx, input) => { - // Fan-out: Analyze each section in parallel - const sectionAnalyses = await Promise.all( - input.sections.map(section => - ctx.agent.sectionAnalyzer.run({ text: section }) - ) - ); - - // Fan-in: Aggregate results - const summary = await ctx.agent.summaryAgent.run({ - analyses: sectionAnalyses - }); - - return { - sections: sectionAnalyses, - summary: summary.overall - }; - } -}); -``` - -**Handling partial failures:** - -```typescript -const results = await Promise.allSettled( - input.items.map(item => - ctx.agent.processingAgent.run({ item }) - ) -); - -const successful = results - .filter(r => r.status === 'fulfilled') - .map(r => r.value); - -const failed = results - .filter(r => r.status === 'rejected') - .map(r => r.reason); - -if (failed.length > 0) { - ctx.logger.warn('Some operations failed', { failed }); -} - -return { successful, failed: failed.length }; -``` - -## Error Handling Strategies - -### Cascading Failures - -By default, errors propagate through the call chain, stopping execution immediately: - -```typescript -const agent = createAgent({ - handler: async (ctx, input) => { - // If validatorAgent throws, execution stops here - const validated = await ctx.agent.validatorAgent.run(input); - - // This line never executes if validation fails - const processed = await ctx.agent.processorAgent.run(validated); - - return processed; - } -}); -``` - -This is the recommended pattern for critical operations where later steps cannot proceed without earlier results. - -### Graceful Degradation - -For optional operations, catch errors and continue with reduced functionality: - -```typescript -const agent = createAgent({ - handler: async (ctx, input) => { - let enrichedData = input.data; - - // Try to enrich, but continue if it fails - try { - const enrichment = await ctx.agent.enrichmentAgent.run({ - data: input.data - }); - enrichedData = enrichment.data; - } catch (error) { - ctx.logger.warn('Enrichment failed, using original data', { - error: error instanceof Error ? error.message : String(error) - }); - } - - // Process with enriched data (or original if enrichment failed) - return await ctx.agent.processorAgent.run({ - data: enrichedData - }); - } -}); -``` - -**When to use graceful degradation:** -- Optional external API calls -- Caching operations (fall back to database) -- Non-critical enrichment services -- Analytics or logging operations - -### Retry Strategies - -Implement retry logic for unreliable operations: - -```typescript -async function callWithRetry( - fn: () => Promise, - maxRetries: number = 3, - delayMs: number = 1000 -): Promise { - for (let attempt = 1; attempt <= maxRetries; attempt++) { - try { - return await fn(); - } catch (error) { - if (attempt === maxRetries) { - throw error; - } - - // Exponential backoff - const delay = delayMs * Math.pow(2, attempt - 1); - await new Promise(resolve => setTimeout(resolve, delay)); - } - } - throw new Error('Retry failed'); -} - -const agent = createAgent({ - handler: async (ctx, input) => { - // Retry unreliable external agent call - const result = await callWithRetry(() => - ctx.agent.externalServiceAgent.run(input) - ); - - return result; - } -}); -``` - -### Fallback Strategies - -Provide alternative paths when primary agents fail: - -```typescript -const agent = createAgent({ - handler: async (ctx, input) => { - try { - // Try primary agent - return await ctx.agent.primaryAgent.run(input); - } catch (error) { - ctx.logger.warn('Primary agent failed, using fallback', { error }); - - // Fall back to alternative agent - return await ctx.agent.fallbackAgent.run(input); - } - } -}); -``` - -## Data Flow & Transformation - -### Output to Input Mapping - -Transform agent outputs to match the expected input of downstream agents: - -```typescript -const agent = createAgent({ - handler: async (ctx, input) => { - // Agent 1 returns { analysisResult: {...} } - const analysis = await ctx.agent.analysisAgent.run({ - text: input.text - }); - - // Agent 2 expects { data: {...}, metadata: {...} } - const processed = await ctx.agent.processingAgent.run({ - data: analysis.analysisResult, - metadata: { - timestamp: new Date().toISOString(), - source: 'analysis-agent' - } - }); - - return processed; - } -}); -``` - -### Schema Compatibility - -Ensure type safety across agent boundaries using schemas: - -```typescript -// enrichmentAgent output schema -const enrichmentOutputSchema = z.object({ - enrichedText: z.string(), - metadata: z.object({ - sentiment: z.number(), - keywords: z.array(z.string()) - }) -}); - -// analysisAgent input schema (compatible with enrichment output) -const analysisInputSchema = z.object({ - enrichedText: z.string(), - metadata: z.object({ - sentiment: z.number(), - keywords: z.array(z.string()) - }) -}); -``` - -When schemas are compatible, TypeScript validates the connection at compile time. - -### Transformation Pipelines - -Build pipelines that progressively transform data: - -```typescript -const transformationAgent = createAgent({ - handler: async (ctx, input) => { - // Raw text → Structured data - const structured = await ctx.agent.parserAgent.run({ - text: input.rawText - }); - - // Structured data → Validated data - const validated = await ctx.agent.validatorAgent.run({ - data: structured.parsed - }); - - // Validated data → Enriched data - const enriched = await ctx.agent.enrichmentAgent.run({ - data: validated.clean - }); - - // Enriched data → Final output - const final = await ctx.agent.formatterAgent.run({ - data: enriched.enriched - }); - - return final; - } -}); -``` - -## Real-World Examples - -### Search + Summarize + Analyze Workflow - -```typescript -const researchAgent = createAgent({ - schema: { - input: z.object({ query: z.string() }), - output: z.object({ - summary: z.string(), - insights: z.array(z.string()), - sources: z.array(z.string()) - }) - }, - handler: async (ctx, input) => { - // Step 1: Search for relevant information - const searchResults = await ctx.agent.searchAgent.run({ - query: input.query, - limit: 10 - }); - - // Step 2: Summarize findings (parallel) - const summaries = await Promise.all( - searchResults.results.map(result => - ctx.agent.summaryAgent.run({ text: result.content }) - ) - ); - - // Step 3: Analyze and extract insights - const analysis = await ctx.agent.analysisAgent.run({ - summaries: summaries.map(s => s.summary), - originalQuery: input.query - }); - - return { - summary: analysis.overallSummary, - insights: analysis.keyInsights, - sources: searchResults.results.map(r => r.url) - }; - } -}); -``` - -### Multi-Step Approval System - -```typescript -const approvalAgent = createAgent({ - schema: { - input: z.object({ - request: z.any(), - requester: z.string() - }) - }, - handler: async (ctx, input) => { - // Step 1: Validate request format - const validation = await ctx.agent.validatorAgent.run({ - request: input.request - }); - - if (!validation.valid) { - return { - approved: false, - reason: 'Invalid request format', - errors: validation.errors - }; - } - - // Step 2: Check user permissions - const permissions = await ctx.agent.permissionsAgent.run({ - userId: input.requester, - action: input.request.action - }); - - if (!permissions.allowed) { - return { - approved: false, - reason: 'Insufficient permissions' - }; - } - - // Step 3: Risk assessment (parallel checks) - const [financialRisk, securityRisk, complianceCheck] = await Promise.all([ - ctx.agent.financialRiskAgent.run(input.request), - ctx.agent.securityRiskAgent.run(input.request), - ctx.agent.complianceAgent.run(input.request) - ]); - - // Step 4: Final approval decision - const decision = await ctx.agent.decisionAgent.run({ - request: input.request, - permissions, - financialRisk, - securityRisk, - complianceCheck - }); - - return decision; - } -}); -``` - -### Data Enrichment Pipeline - -```typescript -const dataEnrichmentAgent = createAgent({ - handler: async (ctx, input) => { - // Start with raw user data - let userData = input.userData; - - // Parallel enrichment from multiple sources - const [ - demographicData, - behavioralData, - preferenceData - ] = await Promise.all([ - ctx.agent.demographicAgent.run({ userId: userData.id }), - ctx.agent.behavioralAgent.run({ userId: userData.id }), - ctx.agent.preferenceAgent.run({ userId: userData.id }) - ]); - - // Merge all data - const merged = { - ...userData, - demographics: demographicData, - behavior: behavioralData, - preferences: preferenceData - }; - - // Generate insights from enriched data - const insights = await ctx.agent.insightsAgent.run({ - enrichedData: merged - }); - - // Store for future use - try { - await ctx.kv.set('enriched-users', userData.id, { - data: merged, - insights: insights, - updatedAt: new Date().toISOString() - }, { ttl: 86400 }); // 24 hours - } catch (error) { - ctx.logger.warn('Failed to cache enriched data', { error }); - } - - return { - enrichedData: merged, - insights: insights - }; - } -}); -``` - -## Subagent Communication - -For parent-child agent hierarchies, the SDK provides specialized patterns. Subagents are useful when agents share a common domain or need coordinated access control. - -### Quick Example - -```typescript -// Call a subagent from anywhere -const result = await ctx.agent.team.members.run({ - action: 'list' -}); - -// From a subagent, access the parent -if (ctx.parent) { - const parentResult = await ctx.parent.run({ - action: 'validate' - }); -} -``` - -For comprehensive coverage of subagent patterns, validation strategies, and best practices, see the [Subagents](/Guides/subagents) guide. - -## Best Practices - -### Keep Agents Focused - -Each agent should have a single, well-defined responsibility: - -```typescript -// Good - focused agents -const validatorAgent = createAgent({ /* validates data */ }); -const enrichmentAgent = createAgent({ /* enriches data */ }); -const analysisAgent = createAgent({ /* analyzes data */ }); - -// Bad - monolithic agent doing everything -const megaAgent = createAgent({ - handler: async (ctx, input) => { - // Validates, enriches, analyzes all in one place - } -}); -``` - -Focused agents are easier to test, reuse, and maintain. - -### Use Schemas for Type Safety - -Define schemas for all agents to enable type-safe communication: - -```typescript -const sourceAgent = createAgent({ - schema: { - output: z.object({ - data: z.string(), - metadata: z.object({ timestamp: z.string() }) - }) - }, - handler: async (ctx, input) => { - return { - data: 'result', - metadata: { timestamp: new Date().toISOString() } - }; - } -}); - -const consumerAgent = createAgent({ - schema: { - input: z.object({ - data: z.string(), - metadata: z.object({ timestamp: z.string() }) - }) - }, - handler: async (ctx, input) => { - // TypeScript knows the exact shape of input - const result = await ctx.agent.sourceAgent.run({}); - - // This is type-safe - TypeScript validates compatibility - return await processData(result); - } -}); -``` - -### Handle Errors Appropriately - -Choose the right error handling strategy for each operation: - -**Fail-fast** for critical operations: -```typescript -// No try-catch - let errors propagate -const validated = await ctx.agent.validatorAgent.run(input); -``` - -**Graceful degradation** for optional operations: -```typescript -try { - await ctx.agent.optionalAgent.run(input); -} catch (error) { - ctx.logger.warn('Optional operation failed', { error }); -} -``` - -### Monitor Performance - -Use logging and tracing to monitor multi-agent workflows: - -```typescript -const agent = createAgent({ - handler: async (ctx, input) => { - const startTime = Date.now(); - - ctx.logger.info('Starting multi-agent workflow', { - sessionId: ctx.sessionId - }); - - const result = await ctx.agent.processingAgent.run(input); - - ctx.logger.info('Workflow completed', { - duration: Date.now() - startTime, - sessionId: ctx.sessionId - }); - - return result; - } -}); -``` - -### Leverage Shared Context - -Agent calls execute within the same session context, sharing state: - -```typescript -const coordinatorAgent = createAgent({ - handler: async (ctx, input) => { - // Store data in thread state - ctx.thread.state.set('startTime', Date.now()); - ctx.thread.state.set('userId', input.userId); - - // Called agents can access the same thread state - const result = await ctx.agent.processingAgent.run(input); - - // All agents share the same sessionId - ctx.logger.info('Session ID:', ctx.sessionId); - - return result; - } -}); -``` - -Use this for: -- Tracking context across agent calls -- Sharing authentication/authorization data -- Maintaining conversation state -- Coordinating distributed workflows - -### Fallback and Error Handling for Classification - -When using LLM-based routing, handle classification failures gracefully: - -```typescript -const routerAgent = createAgent({ - handler: async (ctx, input) => { - try { - const intent = await generateObject({ - model: groq('llama-3.3-70b'), - schema: IntentSchema, - prompt: input.userMessage, - temperature: 0.0 - }); - - // Route based on intent - switch (intent.object.agentType) { - case 'support': - return await ctx.agent.supportAgent.run({ - message: input.userMessage, - context: intent.object.reasoning - }); - - case 'sales': - return await ctx.agent.salesAgent.run({ - message: input.userMessage, - context: intent.object.reasoning - }); - - case 'technical': - return await ctx.agent.technicalAgent.run({ - message: input.userMessage, - context: intent.object.reasoning - }); - } - } catch (error) { - ctx.logger.error('Intent classification failed', { - error: error instanceof Error ? error.message : String(error) - }); - - // Fallback to default agent - return await c.agent.defaultAgent.run({ - message: input.userMessage - }); - } - } -}); -``` - -This ensures your system remains functional even when classification fails. - -## Next Steps - -- [Subagents](/Guides/subagents): Parent-child agent hierarchies and coordination patterns -- [Schema Validation](/Guides/schema-validation): Type-safe schemas for agent inputs and outputs -- [Events](/Guides/events): Monitor agent lifecycle and execution -- [Error Handling](/SDK/error-handling): Comprehensive error handling strategies -- [Core Concepts](/SDK/core-concepts): Detailed agent communication API reference diff --git a/content/v1/archive/architecture.mdx b/content/v1/archive/architecture.mdx deleted file mode 100644 index 286aa573..00000000 --- a/content/v1/archive/architecture.mdx +++ /dev/null @@ -1,546 +0,0 @@ ---- -title: Overview -description: Understanding Agentuity ---- - -## Agentuity Overview - -Agentuity is a cloud platform that lets you run and scale AI agents with enterprise-grade reliability. -Each agent operates in its own isolated, containerized environment, giving you the security and -isolation you need for production workloads. - -Unlike traditional serverless platforms, your agents run for as long as needed, maintaining state -and context throughout their lifecycles. This long-running approach means you can build complex -workflows without worrying about time limits. It's perfect for agents that need extended processing -time, persistent storage, or ongoing access to resources. - -The platform is fundamentally cross-platform, so you can run different agent frameworks (CrewAI, -Langchain, custom agents) side by side in the same ecosystem. Built-in communication channels let -your agents work together seamlessly, regardless of which framework they use. - -## Core Components - -Agentuity consists of five primary components: - -1. **Agent Platform** - The cloud platform for providing agent services, providing: - - Agent communication and routing - - Agent monitoring, logging, telemetry and troubleshooting - - Agent usage analytics and performance insights - - Automatic scaling on-demand based on workload - - Agent services such as KeyValue, Vector storage, AI Gateway and more - -2. **Agent Runtime** - The execution environment where your agents run, providing: - - Isolated, secure virtualized environment for each agent project - - Resource management and optimization - - Long-running support for persistent agents - - Dynamic Storage, Compute and Networking resources - -3. **Command Line Interface (CLI)** - A developer tool that enables: - - Quick agent creation and initialization - - Local development and testing - - Deployment management to the Agentuity cloud - - Integration with external Agentic code tools via MCP - -4. **Software Development Kits (SDKs)** - Libraries that provide: - - Agent-native tools and services integration with the Agent Platform - - Runtime-specific optimizations for JavaScript/TypeScript (Node.js and Bun) - - Integration capabilities with external systems - - Enhanced agent capabilities and extensions which work cross-framework and cross-runtime - -5. **Web Console** - A management interface offering: - - Real-time agent monitoring and metrics - - Deployment and configuration management - - Usage analytics, logging, monitoring and performance insights - - Team collaboration features - -## Built-in Services - -Agentuity includes built-in services that work out of the box. These services are accessible through the agent context (`ctx`) and require minimal configuration. - -### Routing and Connectivity - -| Service | Description | Access | -|---------|-------------|--------| -| HTTP/WebSocket/SSE | Built on Hono framework with automatic load balancing | `router.get()`, `router.post()`, `router.websocket()`, `router.sse()` | -| Email Handling | Define email addresses as routes to trigger agents from incoming messages | `router.email()` | -| SMS Integration | Route SMS messages to agents based on phone numbers | `router.sms()` | -| Cron Scheduling | Schedule periodic agent execution with cron expressions | `router.cron()` | -| Authentication | Configurable authentication and rate limiting per route | Middleware | - -### Storage Layer - -| Service | Description | Access | -|---------|-------------|--------| -| Key-Value Storage | Fast key-value store with TTL support | `ctx.kv` | -| Vector Database | Embeddings storage and semantic search | `ctx.vector` | -| Object Storage | Blob and file storage with public URL generation | `ctx.objectstore` | -| Stream Storage | Large data streaming and processing | `ctx.stream` | - -### Observability - -| Service | Description | Access | -|---------|-------------|--------| -| Structured Logging | Built-in logger with contextual information | `ctx.logger` | -| OpenTelemetry Tracing | Distributed tracing for agent interactions | `ctx.tracer` | -| Real-time Analytics | Execution metrics, performance data, and usage tracking | Web Console | -| Error Tracking | Automatic error capture and reporting | Web Console | - -### AI Gateway - -| Feature | Description | -|---------|-------------| -| Unified LLM Access | Single interface for multiple LLM providers (OpenAI, Anthropic, etc.) | -| Managed API Keys | Centralized credential management for LLM services | -| Model Switching | Change providers without code modifications | -| Usage Tracking | Monitor LLM API usage and costs | - -### Evaluation Framework - -| Feature | Description | -|---------|-------------| -| Real-time Testing | Test agent input/output during execution using `agent.createEval()` | -| Quality Assurance | Automated checks for accuracy, relevance, and compliance | -| Custom Evaluators | Define custom evaluation logic for specific requirements | -| Non-blocking Execution | Evaluations run asynchronously without impacting response times | - -### Frontend Deployment - -| Feature | Description | -|---------|-------------| -| React Integration | Deploy React applications alongside agents | -| Hot Reload | Automatic reload during development | -| CDN Distribution | Built-in CDN for global frontend delivery | -| Custom Domains | Configure custom domains with automatic SSL certificates | - -All services are configured in `agentuity.yaml` and accessed through the agent context. No additional infrastructure setup required. - -## SDK Architecture - -Agentuity v1 uses a monorepo structure with specialized packages for different concerns: - -### Package Overview - -- **`@agentuity/runtime`** - The primary SDK for building agents - - Agent creation and lifecycle management - - Built on Hono framework for routing - - Event system for monitoring - - Evaluation framework for testing - - Session and thread management - -- **`@agentuity/core`** - Shared utilities and types - - Storage abstractions (KV, Vector, Object, Stream) - - Common interfaces and types - - Schema validation support (StandardSchema) - -- **`@agentuity/server`** - Runtime-agnostic server utilities - - Server creation and configuration - - Request/response handling - - Middleware support - -- **`@agentuity/react`** - React components and hooks - - `AgentuityProvider` for app-wide configuration - - `useAgent` hook for calling agents - - `useAgentWebsocket` for WebSocket connections - - `useAgentEventStream` for Server-Sent Events - -- **`@agentuity/cli`** - Command-line tools - - Project scaffolding and management - - Local development server - - Deployment orchestration - -### App & Router Architecture - -Agentuity v1 is built on [Hono](https://hono.dev/), a fast, lightweight web framework. Your agents are organized into an **App** that manages routing, configuration, and lifecycle: - -```typescript -import { createApp } from '@agentuity/runtime'; - -// Create the app -const app = createApp(); - -// The router is available at app.router -app.router.post('/my-agent', async (c) => { - return c.json({ message: 'Hello!' }); -}); - -// Export the server -export default app.server; -``` - -This architecture provides: -- **Type-safe routing** with full TypeScript support -- **Middleware support** for authentication, logging, etc. -- **Automatic service injection** (kv, logger, tracer, etc.) -- **Event system** for monitoring agent executions -- **Code-based trigger configuration** - Cron schedules, email addresses, and SMS numbers are defined in your routes, not in the UI - -## Data Flow - -Agent communication and data flow in Agentuity follow secure, encrypted channels: - -1. **Agent-to-Agent Communication** - Agents can communicate with each other through authenticated, encrypted routing, -regardless of the underlying frameworks or runtimes used. - -2. **External Integrations** - Agents can connect to external systems and data sources through managed -integration points. - -3. **Deployment Pipeline** - Your project code is packaged, containerized, and deployed to the Agentuity -cloud infrastructure with appropriate networking and routing configured automatically. Built-in support for GitHub Actions. - -## Scalability - -Agentuity is designed for enterprise-scale agent deployments: - -- **Horizontal Scaling** - Automatically provision additional resources as demand increases -- **Framework Agnostic** - Scale any type of agent regardless of the underlying framework -- **Load Balancing** - Distribute agent workloads efficiently across available resources -- **Resource Optimization** - Intelligently allocate compute resources based on agent requirements - -## Security Architecture - -Security is foundational to Agentuity's design: - -- **Agent Isolation** - Each agent project operates in its own isolated environment -- **Encrypted Communications** - All agent-to-agent communication is encrypted -- **Secure Deployment** - Protected deployment pipeline from development to production - -## Deployment Targets - -Agentuity supports multiple deployment targets, allowing you to choose where your agents run based on your requirements. The same agent code and configuration work across all targets. - -### Public Cloud - -Deploy to Agentuity's managed infrastructure for immediate scalability: - -- **Global Edge Network** - Agents run on a distributed edge network for low-latency responses -- **Automatic Scaling** - Scale from zero to thousands of requests without configuration -- **Fast Cold Starts** - Sub-100ms cold start times on the global edge -- **Managed Services** - All infrastructure, monitoring, and maintenance handled by Agentuity - -Best for: Rapid deployment, global distribution, and zero infrastructure management. - -### Private Cloud - -Deploy agents to your own Virtual Private Cloud (VPC) while using Agentuity services: - -- **Infrastructure Control** - Agents run in your AWS, GCP, or Azure account -- **Data Security** - Your data never leaves your infrastructure -- **Agentuity Services** - Access storage, observability, and platform features -- **Compliance** - Meet regulatory requirements for data residency - -Best for: Organizations with strict data governance or compliance requirements. - -### Multi-Cloud - -Deploy agents across multiple cloud providers with consistent configuration: - -- **Provider Flexibility** - Run on AWS, GCP, Azure, or combinations -- **Avoid Lock-in** - Move between providers without code changes -- **Geographic Distribution** - Place agents near users across different clouds -- **Consistent Tooling** - Same CLI, SDK, and deployment process everywhere - -Best for: Organizations requiring cloud provider flexibility or multi-region deployments. - -### On-Premises - -Self-host Agentuity infrastructure on your own hardware: - -- **Complete Control** - Full ownership of infrastructure and data -- **Air-Gapped Support** - Run in isolated networks without internet access -- **Custom Hardware** - Deploy on specialized hardware or existing infrastructure -- **Data Sovereignty** - Guarantee data never leaves your physical premises - -Best for: Highly regulated industries, air-gapped environments, or specific hardware requirements. - -### Edge Deployment - -Deploy agents to edge devices for local processing: - -- **Local Processing** - Run agents on Raspberry Pi, laptops, or IoT devices -- **Low Latency** - Process data at the edge without network round-trips -- **Offline Operation** - Agents function without cloud connectivity -- **Resource Efficiency** - Optimized for resource-constrained environments - -Best for: IoT applications, offline scenarios, or latency-sensitive workloads. - -## Project Conventions - -Agentuity projects follow specific conventions to take advantage of the deployment and cloud platform Agentuity offers. Understanding these conventions is important for effective agent development. - -### Project Structure - -Every Agentuity project requires the following core components: - -1. **agentuity.yaml** - The central configuration file that defines: - - Project metadata (name, ID, description) - - Development settings (port, watch patterns) - - Deployment configuration (resources, scaling) - - Bundler settings (language, runtime) - - Agent definitions and routing - -2. **Environment Variables** - Stored in a `.env` file: - - `AGENTUITY_SDK_KEY` - Identifies the SDK level API Key (only used in development to access the Agentuity Cloud) - - `AGENTUITY_PROJECT_KEY` - Identifies the project level API Key - - Additional provider-specific keys (OpenAI, Anthropic, etc.) - -3. **Agent Directory** - Specified in `bundler.agents.dir`: - - Each agent has its own subdirectory - - TypeScript/JavaScript entry points: `agent.ts` and `route.ts` - - Agent-specific configuration and dependencies - -### JavaScript/TypeScript Project Structure - -``` -my-project/ -├── agentuity.yaml # Project configuration -├── .env # Environment variables -├── package.json # Dependencies and scripts -├── app.ts # App entry point -└── src/ - ├── agents/ # Agent directory - │ └── my-agent/ # Individual agent - │ ├── agent.ts # Agent logic (createAgent) - │ └── route.ts # Route definitions (createRouter) - └── apis/ # Optional: Custom API routes - └── status/ - └── route.ts -``` - -### Agent Structure - -Each agent in Agentuity consists of two files that work together: - -#### Understanding the Two-File Pattern - -Every agent directory contains both `agent.ts` and `route.ts`. This separation keeps concerns clear: - -- **`agent.ts`** uses `createAgent()` to define the agent's logic, schemas, and metadata. This is where your business logic lives. -- **`route.ts`** uses `createRouter()` to define HTTP endpoints that invoke the agent. Routes handle HTTP-specific concerns like request parsing and response formatting. - -The route file imports the agent and calls it using `ctx.agent.agentName.run(input)`. The Agentuity bundler automatically discovers both files and registers them together based on the directory structure. - -Routes can exist without agents (such as health check endpoints in `/apis/` directories), but every agent should have a corresponding route file to enable HTTP access. - -#### `agent.ts` - Agent Logic - -Defines the agent's behavior, schema, and metadata: - -```typescript -import { createAgent } from '@agentuity/runtime'; -import { z } from 'zod'; - -export default createAgent({ - schema: { - input: z.object({ - message: z.string() - }), - output: z.object({ - response: z.string() - }) - }, - metadata: { - name: 'My Agent', - description: 'A simple agent' - }, - handler: async (ctx, input) => { - ctx.logger.info('Processing request', { input }); - - return { - response: `Received: ${input.message}` - }; - } -}); -``` - -#### `route.ts` - Route Definitions - -Defines how the agent is exposed via HTTP or other protocols: - -```typescript -import { createRouter } from '@agentuity/runtime'; -import agent from './agent'; - -const router = createRouter(); - -// HTTP routes -router.post('/', async (c) => { - const input = await c.req.json(); - const result = await c.agent.myAgent.run(input); - return c.json(result); -}); - -// Advanced: WebSocket route -router.websocket('/ws', (c) => (ws) => { - ws.onMessage(async (event) => { - const result = await c.agent.myAgent.run(event.data); - ws.send(JSON.stringify(result)); - }); -}); - -export default router; -``` - -#### How Agent Files Work Together - -When an HTTP request arrives, the flow works as follows: - -1. The HTTP request is routed to the appropriate route handler in `route.ts` -2. The route handler optionally validates the input using middleware (such as `zValidator` with the agent's schema) -3. The route calls the agent using `c.agent.myAgent.run(input)` -4. The agent's handler function executes with the validated input -5. The agent returns its output, which flows back through the route to the client - -This architecture provides clear separation: routes handle HTTP concerns (request parsing, validation, response formatting) while agents focus purely on business logic. The agent can be called from multiple routes or by other agents without duplicating logic. - -### App Entry Point - -The `app.ts` file creates and configures your application: - -```typescript -import { createApp } from '@agentuity/runtime'; - -const app = createApp(); - -// Optional: Add app-level event listeners -app.addEventListener('agent.started', (event, agent, ctx) => { - app.logger.info('Agent started', { - agent: agent.metadata.name, - session: ctx.sessionId - }); -}); - -// The router automatically discovers and registers all agents -// based on the directory structure - -export default app.server; -``` - -### Configuration File (agentuity.yaml) - -The `agentuity.yaml` file is the heart of your project, defining how it behaves in development and production: - -```yaml -version: ">=0.0.0" # Minimum CLI version required -project_id: "proj_..." # Unique project identifier -name: "My Project" # Human-readable project name -description: "..." # Optional project description - -# Development configuration -development: - port: 3000 # Local development server port - watch: - enabled: true # Auto-reload on file changes - files: ["src/**/*.ts"] # Files to watch - -# Deployment configuration -deployment: - resources: - memory: "1Gi" # Memory allocation - cpu: "1000m" # CPU allocation - -# Bundler configuration -bundler: - language: "javascript" # Programming language (javascript only in v1) - runtime: "nodejs" # Runtime environment (nodejs or bunjs) - agents: - dir: "src/agents" # Directory where agents are located - -# Agents configuration -agents: - - id: "agent_..." # Unique agent identifier - name: "My Agent" # Human-readable agent name - description: "..." # Optional agent description -``` - -### Key Concepts in v1 - -#### Sessions and Threads - -- **Session** - Represents a single execution context with a unique `sessionId` -- **Thread** - Represents a conversation thread for multi-turn interactions -- Access via `ctx.session` and `ctx.thread` in agent handlers - -#### Event System - -Monitor agent lifecycle with event listeners: - -```typescript -// Agent-level events -agent.addEventListener('started', (event, agent, ctx) => { - // Called when agent starts -}); - -agent.addEventListener('completed', (event, agent, ctx) => { - // Called when agent completes successfully -}); - -agent.addEventListener('errored', (event, agent, ctx, error) => { - // Called when agent encounters an error -}); - -// App-level events -app.addEventListener('agent.started', (event, agent, ctx) => { - // Called for any agent start -}); -``` - -#### Evaluation Framework - -Test and validate agent outputs automatically: - -```typescript -agent.createEval({ - metadata: { - name: 'quality-check', - description: 'Validates output quality' - }, - handler: async (ctx, input, output) => { - // Evaluate the output - const score = calculateQuality(output); - - return { - success: true, - score: score, - metadata: { /* additional info */ } - }; - } -}); -``` - -#### Schema Validation - -Define and validate input/output schemas using any StandardSchema-compatible library (Zod, Valibot, ArkType, etc.): - -```typescript -import { z } from 'zod'; - -const agent = createAgent({ - schema: { - input: z.object({ - name: z.string(), - age: z.number().optional() - }), - output: z.object({ - greeting: z.string() - }) - }, - handler: async (ctx, input) => { - // input is fully typed and validated - return { - greeting: `Hello ${input.name}!` - }; - } -}); -``` - -### Why These Conventions Matter - -These conventions enable several key capabilities: - -1. **Consistent Development Experience** - Standardized structure makes it easier to work across projects -2. **Automated Deployment** - The CLI can package and deploy your project without additional configuration -3. **Framework Flexibility** - Use any agent framework while maintaining compatibility with the platform -4. **Type Safety** - Full TypeScript support throughout the stack -5. **Scalability** - Clear separation of concerns makes it easy to organize complex agent systems - -TODO: Add examples showing integration with frontend frameworks (Next.js, Svelte, vanilla JS, etc.) for building UIs that call agents diff --git a/content/v1/archive/context-types.mdx b/content/v1/archive/context-types.mdx deleted file mode 100644 index 08b6b4cb..00000000 --- a/content/v1/archive/context-types.mdx +++ /dev/null @@ -1,181 +0,0 @@ ---- -title: Understanding Context Types -description: Learn about Agentuity's two distinct context types - AgentContext for business logic and Router Context for HTTP handling ---- - -Agentuity v1 uses two distinct context types depending on where you're writing code. Understanding when and how to use each context is fundamental to working effectively with the SDK. - -## The Two Context Types - -Agentuity provides two context types, each designed for a specific purpose: - -- **AgentContext**: Used in `agent.ts` files for business logic (no HTTP access) -- **Router Context (Hono)**: Used in `route.ts` files for HTTP handling (has HTTP + agent services) - -**Important:** The distinction is **type-based**, not **name-based**. In SDK examples, we use `ctx` for agent handlers and `c` for router handlers to visually distinguish the two context types. What differs is the **type** and **capabilities**. - -## AgentContext - For Business Logic - -The `AgentContext` is used in agent handlers defined in `agent.ts` files. This context is designed for pure business logic and provides access to Agentuity services without any HTTP-specific functionality. - -**Type:** `AgentContext` - -**Used in:** `agent.ts` files - -**Provides:** -- Agent services (`.agent`, `.kv`, `.vector`, `.objectstore`, etc.) -- State management (`.state`, `.session`, `.thread`) -- Logging and tracing (`.logger`, `.tracer`) -- No HTTP access - input comes through validated parameters - -**Example:** - -```typescript -import { createAgent } from '@agentuity/runtime'; - -const agent = createAgent({ - handler: async (ctx, input) => { - // Business logic only - no HTTP access - ctx.logger.info('Processing'); - await ctx.kv.set('key', 'value'); - return { result: 'done' }; // Direct return - } -}); -``` - -In agent handlers, the input is passed as a separate parameter and is already validated against your schema. The context provides services but no HTTP-specific methods. - -## RouterContext - For HTTP Handling - -The Router Context is Hono's Context object, used in route handlers defined in `route.ts` files. This context extends Hono's functionality with Agentuity services, giving you both HTTP capabilities and access to the agent system. - -**Type:** Hono Context (extends `Context`) - -**Used in:** `route.ts` files - -**Provides:** -- HTTP request access (`.req`) -- HTTP response builders (`.json()`, `.text()`, `.html()`, etc.) -- Agent services (`.agent`, `.kv`, `.vector`, `.objectstore`, etc.) -- Logging and tracing (`.logger`, `.tracer`) - -**Example:** - -```typescript -import { createRouter } from '@agentuity/runtime'; - -const router = createRouter(); - -router.post('/api', async (c) => { - // HTTP handling + agent services - const body = await c.req.json(); // HTTP request access - const result = await c.agent.processor.run(body); - return c.json({ result }); // HTTP response -}); -``` - -In route handlers, you access request data through `c.req` and return responses using Hono's builder methods like `c.json()`. - -## Key Differences - -Here's a quick comparison of the two context types: - -| Feature | Router Context (Hono) | Agent Context | -|---------|----------------------|---------------| -| **Type** | Hono Context | `AgentContext` | -| **Used in** | `route.ts` files | `agent.ts` files | -| **Request access** | `c.req` (Hono Request) | Direct `input` parameter (validated) | -| **Response** | Builder methods (`.json()`, `.text()`) | Direct returns | -| **Services** | `.agent`, `.kv`, `.logger`, etc. | `.agent`, `.kv`, `.logger`, etc. | -| **State management** | Via Hono middleware | Built-in (`.state`, `.session`, `.thread`) | - -Both contexts provide access to the same Agentuity services, but differ in how they handle input and output. - -## Common Patterns - -### Calling Agents from Routes - -A common pattern is to receive HTTP requests in routes, then delegate business logic to agents: - -```typescript -// route.ts - HTTP handling -router.post('/process', async (c) => { - const body = await c.req.json(); - - // Call agent to handle business logic - const result = await c.agent.processor.run({ - data: body.data - }); - - return c.json({ result }); -}); -``` - -```typescript -// agent.ts - Business logic -const processor = createAgent({ - handler: async (ctx, input) => { - // Pure business logic - ctx.logger.info('Processing data', { input }); - const processed = await processData(input.data); - return { processed }; - } -}); -``` - -### When to Use Which Context - -**Use AgentContext when:** -- Writing reusable business logic -- Building agents that can be called from multiple sources (routes, schedules, other agents) -- You need state management (`.state`, `.session`, `.thread`) -- You don't need HTTP-specific functionality - -**Use Router Context when:** -- Handling HTTP requests and responses -- Need access to headers, cookies, or raw request data -- Building REST APIs or webhooks -- Need HTTP-specific response types (redirects, streaming, etc.) - -### Naming Conventions - -In SDK examples, we use a naming convention to visually distinguish the two context types: - -- **Agent handlers**: Use `ctx` (e.g., `handler: async (ctx, input) => { ... }`) -- **Router handlers**: Use `c` (e.g., `router.get('/api', async (c) => { ... })`) - -This convention makes it immediately clear which context type you're working with when reading code. However, you can use any variable name you prefer - `c`, `ctx`, `context`, etc. The **type** is what matters, not the name. - -```typescript -// Agent handler - uses ctx in SDK examples -handler: async (ctx, input) => { ... } - -// Route handler - uses c in SDK examples -router.get('/api', async (c) => { ... }) -``` - -When a context parameter is required but unused in your handler, use an underscore prefix: - -```typescript -// Context required but unused -handler: async (_ctx, input) => { - return { result: input.value * 2 }; -} -``` - -## Best Practices - -1. **Be consistent with naming**: The SDK examples use `ctx` for agent handlers and `c` for router handlers. This convention helps visually distinguish the two context types. Stick with this convention for consistency unless your team prefers otherwise. - -2. **Focus on types, not names**: Don't assume a parameter is one context type or another based on its variable name. Check the type definition. - -3. **Separate concerns**: Keep HTTP logic in routes, business logic in agents. This makes your code more testable and reusable. - -4. **Use underscore prefix for unused parameters**: If your route handler doesn't use the context, name it `_c` to indicate this intentionally. - -5. **Type your agent handlers**: Always explicitly type the context parameter in agent handlers as `AgentContext` for clarity: - ```typescript - handler: async (ctx: AgentContext, input) => { ... } - ``` - -6. **Let TypeScript guide you**: If you try to access `ctx.req` in an agent handler or `c.state` in a route handler, TypeScript will catch these errors at compile time. \ No newline at end of file diff --git a/content/v1/archive/evaluations.mdx b/content/v1/archive/evaluations.mdx deleted file mode 100644 index 429b930a..00000000 --- a/content/v1/archive/evaluations.mdx +++ /dev/null @@ -1,919 +0,0 @@ ---- -title: Evaluations -description: Automatically test and validate agent outputs for quality, accuracy, and compliance ---- - -The Agentuity SDK includes a built-in evaluation framework for automatically testing agent outputs. Evaluations run after each agent execution to validate quality, check compliance, monitor performance, and ensure outputs meet your requirements. - -Evals execute asynchronously without blocking agent responses, making them ideal for quality assurance in production environments. - -## What are Evaluations? - -Evaluations (evals) are automated tests that run after your agent completes. They receive the agent's input and output, then return a pass/fail result or quality score. - -**Key characteristics:** - -- **Automatic execution**: Evals run automatically after agent completion -- **Non-blocking**: Execute asynchronously using `waitUntil()`, so they don't delay responses -- **Type-safe**: Handler signatures automatically match your agent's schemas -- **Full context**: Access to logging, storage, tracing, and all agent services -- **Independent**: Multiple evals can run on a single agent; errors in one don't affect others - -**Use cases:** -- Quality checking (accuracy, relevance, completeness of your agent's outputs) -- Compliance validation (PII detection, content policy enforcement) -- Performance monitoring (response time, resource usage across your workflow) -- RAG system quality (hallucination detection, faithfulness, contextual relevancy) -- A/B testing and regression testing of agent behavior - -## Creating Evaluations - -Evaluations are created using the `createEval()` method on an agent instance. - -```typescript -import { createAgent } from '@agentuity/runtime'; -import { z } from 'zod'; - -const agent = createAgent({ - schema: { - input: z.object({ question: z.string() }), - output: z.object({ answer: z.string(), confidence: z.number() }), - }, - handler: async (ctx, input) => { - const answer = await generateAnswer(input.question); - return { answer, confidence: 0.95 }; - }, -}); - -// Add an evaluation -agent.createEval({ - metadata: { - name: '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 ? 'Confidence acceptable' : 'Confidence too low' - } - }; - }, -}); -``` - -**Configuration:** - -```typescript -agent.createEval({ - metadata: { - name: string; // Required: eval name - description?: string; // Optional: what this eval checks - }, - handler: EvalFunction; // Required: eval logic -}); -``` - - -**Automatic Metadata**: The SDK automatically generates internal metadata (id, version, identifier, filename) for tracking and versioning. - - -## Eval Handler Signatures - -The eval handler signature automatically adapts based on your agent's schema configuration: - -| Agent Schema | Handler Signature | Use Case | -|--------------|------------------|----------| -| Input + Output | `(ctx, input, output) => EvalRunResult` | Most common - validate both input and output | -| Input only | `(ctx, input) => EvalRunResult` | Validate input format/content | -| Output only | `(ctx, output) => EvalRunResult` | Validate output quality | -| No schema | `(ctx) => EvalRunResult` | Check execution state/metrics | - -**Example: Input and Output Schemas** - -```typescript -const agent = createAgent({ - schema: { - input: z.object({ query: z.string() }), - output: z.object({ result: z.string() }), - }, - handler: async (ctx, input) => { - return { result: `Processed: ${input.query}` }; - }, -}); - -agent.createEval({ - metadata: { name: 'query-in-result' }, - handler: async (ctx, input, output) => { - // Handler receives both input and output - const queryInResult = output.result.includes(input.query); - - return { - success: true, - passed: queryInResult, - metadata: { - reason: queryInResult ? 'Query found in result' : 'Query missing from result' - } - }; - }, -}); -``` - -**Example: Input Schema Only** - -```typescript -const agent = createAgent({ - schema: { - input: z.object({ message: z.string() }), - }, - handler: async (ctx, input) => { - return `Received: ${input.message}`; - }, -}); - -agent.createEval({ - metadata: { name: 'input-validation' }, - handler: async (ctx, input) => { - // Handler receives only input - const isValid = input.message.length > 0 && input.message.length <= 500; - - return { - success: true, - passed: isValid, - metadata: { length: input.message.length, maxLength: 500 } - }; - }, -}); -``` - - -**Type Safety**: TypeScript automatically infers the correct handler signature based on your agent's schemas. Attempting to access unavailable parameters results in compile-time errors. - - -## Eval Results - -Evals can return three types of results: - -### Binary Pass/Fail - -```typescript -{ - success: true, - passed: boolean, - metadata?: Record -} -``` - -Use for clear yes/no validation (compliance checks, format verification). - -```typescript -agent.createEval({ - metadata: { name: 'length-check' }, - handler: async (ctx, input, output) => { - const passed = output.answer.length >= 50; - return { - success: true, - passed, - metadata: { - actualLength: output.answer.length, - minimumLength: 50 - } - }; - }, -}); -``` - -### Score-Based - -```typescript -{ - success: true, - score: number, // 0-1 range - metadata?: Record -} -``` - -Use for quality measurement and A/B testing. - -```typescript -agent.createEval({ - metadata: { name: 'quality-score' }, - handler: async (ctx, input, output) => { - let score = 0; - if (output.answer.length >= 100) score += 0.4; - if (output.answer.includes(input.question)) score += 0.3; - if (output.answer.includes('conclusion')) score += 0.3; - - return { success: true, score, metadata: { breakdown: 'length+relevance+completeness' } }; - }, -}); -``` - - -**Score Range**: Scores should be between 0 and 1, where 0 is worst and 1 is best. - - -### Error Results - -```typescript -{ - success: false, - error: string -} -``` - -Use when the eval itself fails to execute. - -```typescript -agent.createEval({ - metadata: { name: 'external-validation' }, - handler: async (ctx, input, output) => { - try { - const response = await fetch('https://api.example.com/validate', { - method: 'POST', - body: JSON.stringify({ text: output.answer }) - }); - - if (!response.ok) { - return { success: false, error: `Service returned ${response.status}` }; - } - - const result = await response.json(); - return { success: true, passed: result.valid }; - } catch (error) { - return { success: false, error: error.message }; - } - }, -}); -``` - -## Accessing Context - -Eval handlers receive the same context (`EvalContext`) as agent handlers: - -**Available Properties:** -- **Identifiers**: `c.sessionId`, `c.agentName` -- **Storage**: `c.kv`, `c.vector`, `c.objectstore`, `c.stream` -- **Observability**: `c.logger`, `c.tracer` -- **State**: `c.session`, `c.thread`, `c.state` -- **Agents**: `c.agent`, `c.current`, `c.parent` -- **Utilities**: `c.waitUntil()` - -**Example Using Multiple Context Features:** - -```typescript -agent.createEval({ - metadata: { name: 'comprehensive-tracker' }, - handler: async (ctx, input, output) => { - const startTime = c.state.get('startTime') as number; - const duration = Date.now() - startTime; - - // Store metrics - await c.kv.set('performance', c.sessionId, { - duration, - timestamp: Date.now(), - agentName: c.agentName - }); - - // Log with tracer - c.tracer.startActiveSpan('eval-tracking', (span) => { - span.setAttribute('duration_ms', duration); - span.end(); - }); - - // Log result - ctx.logger.info('Performance tracked', { sessionId: c.sessionId, duration }); - - return { - success: true, - passed: duration < 5000, - metadata: { durationMs: duration } - }; - }, -}); -``` - -## Multiple Evaluations - -Agents can have multiple evaluations that all run independently after each execution. - -```typescript -const agent = createAgent({ - schema: { - input: z.object({ text: z.string() }), - output: z.object({ - summary: z.string(), - keywords: z.array(z.string()) - }), - }, - handler: async (ctx, input) => { - return { - summary: generateSummary(input.text), - keywords: extractKeywords(input.text) - }; - }, -}); - -// Eval 1: Summary length -agent.createEval({ - metadata: { name: 'summary-length' }, - handler: async (ctx, input, output) => { - const passed = output.summary.length >= 20 && output.summary.length <= 200; - return { - success: true, - passed, - metadata: { length: output.summary.length, min: 20, max: 200 } - }; - }, -}); - -// Eval 2: Quality score -agent.createEval({ - metadata: { name: 'overall-quality' }, - handler: async (ctx, input, output) => { - let score = 0; - if (output.summary.length >= 50) score += 0.5; - if (output.keywords.length >= 3) score += 0.5; - - return { success: true, score }; - }, -}); -``` - -**File Organization:** - -For agents with multiple evals, use a separate `eval.ts` file: - -``` -agents/ - my-agent/ - agent.ts # Agent definition - eval.ts # All evals for this agent - route.ts # Route definitions -``` - - -**Independent Execution**: Errors in one eval don't prevent others from running. The SDK catches and logs all eval errors. - - -## Common Patterns - -### Task Completion Assessment - -Determine if the agent actually fulfilled the user's objective, not just whether it ran without errors. Uses LLM-as-judge to evaluate end-to-end success: - -```typescript -import { generateObject } from 'ai'; -import { openai } from '@ai-sdk/openai'; -import { z } from 'zod'; - -const customerServiceAgent = createAgent({ - schema: { - input: z.object({ - customerQuery: z.string(), - context: z.object({ - orderId: z.string().optional(), - customerId: z.string() - }) - }), - output: z.object({ - response: z.string(), - actionsTaken: z.array(z.string()) - }) - }, - handler: async (ctx, input) => { - // Agent processes customer request - return { - response: "Your order has been cancelled and refund processed.", - actionsTaken: ["cancelled_order", "issued_refund"] - }; - } -}); - -customerServiceAgent.createEval({ - metadata: { - name: 'task-completion-check', - description: 'Evaluates if agent successfully completed customer request' - }, - handler: async (ctx, input, output) => { - const { object } = await generateObject({ - model: openai('gpt-5-nano'), - schema: z.object({ - completed: z.boolean(), - reasoning: z.string(), - missingSteps: z.array(z.string()) - }), - prompt: `Evaluate if the agent successfully completed the customer's request. - -Customer Query: ${input.customerQuery} -Agent Response: ${output.response} -Actions Taken: ${output.actionsTaken.join(', ')} - -Did the agent fully address the customer's needs? Consider: -- Was the request understood correctly? -- Were appropriate actions taken? -- Was the response clear and helpful? -- Are there missing steps or incomplete resolution?` - }); - - return { - success: true, - passed: object.completed, - metadata: { - reasoning: object.reasoning, - missingSteps: object.missingSteps - } - }; - } -}); -``` - -### Hallucination Detection (Reference-Based) - -Verify that agent output is grounded in retrieved context, detecting unsupported claims. This pattern stores retrieved documents in `c.state` during handler execution for eval access: - -```typescript -import { generateObject } from 'ai'; -import { openai } from '@ai-sdk/openai'; -import { z } from 'zod'; - -const ragAgent = createAgent({ - schema: { - input: z.object({ question: z.string() }), - output: z.object({ - answer: z.string(), - sources: z.array(z.string()) - }) - }, - handler: async (ctx, input) => { - // Retrieve relevant documents - const results = await c.vector.search('knowledge-base', { - query: input.question, - limit: 3 - }); - - // Store retrieved context for eval access - c.state.set('retrievedDocs', results.map(r => r.metadata?.text || '')); - - // Generate answer using LLM + context - const answer = await generateAnswer(input.question, results); - - return { - answer, - sources: results.map(r => r.id) - }; - } -}); - -ragAgent.createEval({ - metadata: { - name: 'hallucination-check', - description: 'Detects claims not supported by retrieved sources' - }, - handler: async (ctx, input, output) => { - // Access retrieved documents from handler execution - const retrievedDocs = c.state.get('retrievedDocs') as string[]; - - const { object } = await generateObject({ - model: openai('gpt-5-nano'), - schema: z.object({ - isGrounded: z.boolean(), - unsupportedClaims: z.array(z.string()), - score: z.number().min(0).max(1) - }), - prompt: `Check if this answer is fully supported by the source documents. - -Question: ${input.question} -Answer: ${output.answer} - -Source Documents: -${retrievedDocs.join('\n\n')} - -Identify any claims in the answer that are NOT supported by the sources.` - }); - - return { - success: true, - score: object.score, - metadata: { - isGrounded: object.isGrounded, - unsupportedClaims: object.unsupportedClaims - } - }; - } -}); -``` - - -**State Persistence**: Data stored in `c.state` during agent execution persists through to eval handlers, enabling patterns like passing retrieved documents to hallucination checks. - - -### Compliance Validation - -Check for policy violations or sensitive content: - -```typescript -agent.createEval({ - metadata: { name: 'content-safety' }, - handler: async (ctx, input, output) => { - const profanityList = ['badword1', 'badword2']; - const piiPatterns = { - email: /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/, - phone: /\b\d{3}[-.]?\d{3}[-.]?\d{4}\b/, - ssn: /\b\d{3}-\d{2}-\d{4}\b/ - }; - - const violations = []; - const lowerOutput = output.answer.toLowerCase(); - - // Check profanity - for (const word of profanityList) { - if (lowerOutput.includes(word)) { - violations.push(`Profanity: ${word}`); - } - } - - // Check PII - for (const [type, pattern] of Object.entries(piiPatterns)) { - if (pattern.test(output.answer)) { - violations.push(`PII: ${type}`); - } - } - - return { - success: true, - passed: violations.length === 0, - metadata: { violations, violationCount: violations.length } - }; - }, -}); -``` - -### RAG Quality Metrics - -For retrieval-augmented generation systems, evaluate three distinct quality dimensions with focused, independent evals: - -```typescript -import { generateObject } from 'ai'; -import { openai } from '@ai-sdk/openai'; -import { z } from 'zod'; - -const ragAgent = createAgent({ - schema: { - input: z.object({ query: z.string() }), - output: z.object({ - answer: z.string(), - confidence: z.number() - }) - }, - handler: async (ctx, input) => { - const results = await c.vector.search('docs', { - query: input.query, - limit: 5, - similarity: 0.7 - }); - - // Store for eval access - c.state.set('retrievedResults', results); - - const answer = await generateAnswer(input.query, results); - - return { answer, confidence: 0.85 }; - } -}); - -// Eval 1: Contextual Relevancy - were the right docs retrieved? -ragAgent.createEval({ - metadata: { - name: 'contextual-relevancy', - description: 'Evaluates if retrieved documents are relevant to query' - }, - handler: async (ctx, input, output) => { - const results = c.state.get('retrievedResults') as VectorSearchResult[]; - - const { object } = await generateObject({ - model: openai('gpt-5-nano'), - schema: z.object({ - score: z.number().min(0).max(1), - irrelevantDocs: z.array(z.number()) - }), - prompt: `Rate how relevant these retrieved documents are to the query. - -Query: ${input.query} - -Documents: -${results.map((r, i) => `${i + 1}. ${r.metadata?.text}`).join('\n')} - -Score 0-1 for overall relevance. List indices of irrelevant docs.` - }); - - return { - success: true, - score: object.score, - metadata: { irrelevantDocs: object.irrelevantDocs } - }; - } -}); - -// Eval 2: Answer Relevancy - does the answer address the question? -ragAgent.createEval({ - metadata: { - name: 'answer-relevancy', - description: 'Evaluates if answer addresses the question' - }, - handler: async (ctx, input, output) => { - const { object } = await generateObject({ - model: openai('gpt-5-nano'), - schema: z.object({ - score: z.number().min(0).max(1), - reasoning: z.string() - }), - prompt: `Rate how well this answer addresses the question. - -Question: ${input.query} -Answer: ${output.answer} - -Score 0-1 for relevancy. Explain your reasoning.` - }); - - return { - success: true, - score: object.score, - metadata: { reasoning: object.reasoning } - }; - } -}); - -// Eval 3: Faithfulness - is answer faithful to sources? -ragAgent.createEval({ - metadata: { - name: 'faithfulness', - description: 'Checks if answer contains information not in sources' - }, - handler: async (ctx, input, output) => { - const results = c.state.get('retrievedResults') as VectorSearchResult[]; - const sources = results.map(r => r.metadata?.text).join('\n\n'); - - const { object } = await generateObject({ - model: openai('gpt-5-nano'), - schema: z.object({ - score: z.number().min(0).max(1), - hallucinations: z.array(z.string()) - }), - prompt: `Check if the answer contains information not in the sources. - -Sources: -${sources} - -Answer: ${output.answer} - -Score 0-1 for faithfulness. List any hallucinated claims.` - }); - - return { - success: true, - score: object.score, - metadata: { hallucinations: object.hallucinations } - }; - } -}); -``` - -**Key Pattern**: Three separate evals, each focused on one quality dimension. This makes it easy to identify specific issues (retrieval vs. generation vs. grounding) and allows independent scoring. - -## Error Handling - -Evals should handle errors gracefully to avoid breaking the eval pipeline. - -**Recommended Pattern:** - -```typescript -agent.createEval({ - metadata: { name: 'safe-external-check' }, - handler: async (ctx, input, output) => { - try { - const response = await fetch('https://api.example.com/validate', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ text: output.answer }), - signal: AbortSignal.timeout(3000) - }); - - if (!response.ok) { - return { success: false, error: `Service error: ${response.status}` }; - } - - const result = await response.json(); - return { success: true, passed: result.isValid }; - } catch (error) { - ctx.logger.error('External validation failed', { error: error.message }); - return { success: false, error: error.message }; - } - }, -}); -``` - -**Fallback Pattern:** - -```typescript -agent.createEval({ - metadata: { name: 'resilient-eval' }, - handler: async (ctx, input, output) => { - // Try primary method - try { - const result = await validateWithLLM(output.answer); - return { success: true, score: result.score, metadata: { method: 'llm' } }; - } catch (error) { - ctx.logger.warn('LLM validation failed, using fallback'); - } - - // Fallback to rule-based - try { - const score = calculateRuleBasedScore(output.answer); - return { success: true, score, metadata: { method: 'rules-fallback' } }; - } catch (error) { - return { success: false, error: 'All validation methods failed' }; - } - }, -}); -``` - -## How Evals Execute - -**Execution Timeline:** - -``` -1. HTTP request arrives - ↓ -2. Agent handler executes - ↓ -3. Output validated against schema - ↓ -4. Agent fires 'completed' event - ↓ -5. Evals scheduled via waitUntil() - │ - ├→ 6. Response returned immediately - │ - └→ 7. Evals execute asynchronously - ↓ - 8. Results sent to tracking service -``` - -**Key Points:** -- Evals run after the response is sent to the caller -- Uses `c.waitUntil()` to avoid blocking -- Each eval receives validated input/output -- Results are logged and tracked automatically - - -**Non-Blocking Design**: Evals execute asynchronously after the response is sent. Users get immediate responses while quality checks run in the background. - - -## Best Practices - -### When to Use Evals - -- **Quality assurance**: Validate completeness, relevance, format -- **Compliance**: Detect PII, check content policies -- **Performance**: Track execution time, resource usage -- **Testing**: A/B testing, regression testing - -### Eval Design - -**Keep evals focused on a single concern:** - -```typescript -// Good: Single-purpose -agent.createEval({ - metadata: { name: 'length-check' }, - handler: async (ctx, input, output) => { - return { success: true, passed: output.answer.length >= 50 }; - }, -}); - -// Avoid: Multiple concerns in one eval -agent.createEval({ - metadata: { name: 'everything-check' }, - handler: async (ctx, input, output) => { - const lengthOk = output.answer.length >= 50; - const hasKeywords = checkKeywords(output); - const sentiment = analyzeSentiment(output); - // Too many checks in one eval - }, -}); -``` - -**Use descriptive metadata:** - -```typescript -// Good -metadata: { - name: 'minimum-length-validation', - description: 'Ensures response meets 50 character minimum for quality' -} - -// Avoid -metadata: { name: 'check1' } -``` - -**Include helpful metadata in results:** - -```typescript -// Good: Detailed metadata -return { - success: true, - passed: false, - metadata: { - actualLength: output.answer.length, - minimumLength: 50, - deficit: 50 - output.answer.length, - reason: 'Response too short by 15 characters' - } -}; - -// Avoid: No context -return { success: true, passed: false }; -``` - -### Performance Considerations - -**Evals don't block responses:** -- Heavy computations are acceptable -- Network calls won't delay users -- Complex LLM evaluations can run without impact - -**Consider LLM costs:** -- LLM-as-judge evals consume API tokens -- Set appropriate budgets for high-traffic agents -- Consider caching common evaluations -- Use smaller models for simple checks - -### Performance Monitoring Pattern - -Track execution time and resource usage: - -```typescript -agent.createEval({ - metadata: { name: 'performance-monitor' }, - handler: async (ctx, input, output) => { - const startTime = c.state.get('startTime') as number; - const duration = startTime ? Date.now() - startTime : 0; - - // Store metrics - await c.kv.set('metrics', `perf-${c.sessionId}`, { - duration, - timestamp: Date.now(), - inputSize: JSON.stringify(input).length, - outputSize: JSON.stringify(output).length - }); - - return { - success: true, - passed: duration < 5000, - metadata: { durationMs: duration, threshold: 5000 } - }; - }, -}); -``` - -### A/B Testing Pattern - -Compare different approaches: - -```typescript -agent.createEval({ - metadata: { name: 'ab-test-tracker' }, - handler: async (ctx, input, output) => { - const variant = c.state.get('variant') as 'A' | 'B'; - if (!variant) { - return { success: false, error: 'No variant specified' }; - } - - let score = 0; - if (output.answer.length >= 100) score += 0.5; - if (output.confidence >= 0.8) score += 0.5; - - // Store for analysis - await c.kv.set('ab-test', `${variant}-${c.sessionId}`, { - variant, - score, - timestamp: Date.now() - }); - - return { success: true, score, metadata: { variant, testGroup: variant } }; - }, -}); -``` - -## Additional Resources - -- [API Reference](/SDKs/javascript/api-reference#evaluations): Detailed type signatures and interfaces -- [Events Guide](/SDKs/javascript/events): Monitor eval execution with event listeners -- [Schema Validation](/SDKs/javascript/schema-validation): Learn more about input/output validation -- [Core Concepts](/SDKs/javascript/core-concepts): Understanding agent architecture diff --git a/content/v1/archive/events.mdx b/content/v1/archive/events.mdx deleted file mode 100644 index 284a992b..00000000 --- a/content/v1/archive/events.mdx +++ /dev/null @@ -1,685 +0,0 @@ ---- -title: Events -description: Lifecycle hooks for monitoring and extending agent behavior ---- - -Events provide lifecycle hooks for monitoring and extending agent behavior at three scopes: agent-level for individual executions, app-level for global tracking, and thread/session for state management. Use events for logging, metrics collection, analytics, and error tracking. All event listeners execute sequentially and support async operations. - -For more on sessions and threads, see [Core Concepts](/Introduction/core-concepts). For additional event examples, see the [Examples](/Examples) page. - -## Agent Events - -Agent events track the execution lifecycle of individual agents. Three events are available: `started` (when execution begins), `completed` (when execution finishes successfully), and `errored` (when an error occurs). Register listeners using `agent.addEventListener()` for agent-specific monitoring and validation. - -### Basic Agent Events - -Track agent execution with lifecycle events: - -```typescript -import { createAgent } from '@agentuity/runtime'; -import { z } from 'zod'; - -const agent = createAgent({ - 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}` }; - } -}); - -// Track when agent starts -agent.addEventListener('started', (eventName, agent, ctx) => { - ctx.state.set('startTime', Date.now()); - c.logger.info('Agent started', { - agentName: agent.metadata.name, - sessionId: c.sessionId - }); -}); - -// Track successful completion -agent.addEventListener('completed', (eventName, agent, ctx) => { - const startTime = c.state.get('startTime') as number; - const duration = Date.now() - startTime; - - c.logger.info('Agent completed', { - agentName: agent.metadata.name, - duration, - sessionId: c.sessionId - }); -}); - -// Track errors -agent.addEventListener('errored', (eventName, agent, ctx, error) => { - const startTime = c.state.get('startTime') as number; - const duration = Date.now() - startTime; - - c.logger.error('Agent failed', { - agentName: agent.metadata.name, - duration, - error: error.message, - sessionId: c.sessionId - }); -}); - -export default agent; -``` - -**Key Points:** -- Event listeners receive the event name, agent instance, and context -- `errored` listeners also receive the error object -- Use `c.state` to share data between event listeners -- Events execute sequentially in registration order - -### Agent Validation with Events - -Use the `completed` event to validate output quality without blocking execution: - -```typescript -import { createAgent } from '@agentuity/runtime'; -import { z } from 'zod'; - -const agent = createAgent({ - schema: { - input: z.object({ query: z.string() }), - output: z.object({ - answer: z.string(), - confidence: z.number() - }) - }, - handler: async (ctx, input) => { - // Process query and return result - return { - answer: `Answer to: ${input.query}`, - confidence: 0.85 - }; - } -}); - -// Validate output quality in completed event -agent.addEventListener('completed', (eventName, agent, ctx) => { - // Access the validated output from context state - const output = c.state.get('_evalOutput') as { answer: string; confidence: number }; - - // Check confidence threshold - if (output.confidence < 0.7) { - ctx.logger.warn('Low confidence output detected', { - confidence: output.confidence, - threshold: 0.7, - agentName: agent.metadata.name - }); - } - - // Check answer length - if (output.answer.length < 10) { - ctx.logger.warn('Suspiciously short answer', { - answerLength: output.answer.length, - agentName: agent.metadata.name - }); - } -}); - -export default agent; -``` - -**Key Points:** -- Validated input/output stored in `c.state` as `_evalInput` and `_evalOutput` -- Event listeners should log warnings, not throw errors -- Keep validation logic lightweight to avoid blocking - -For automated quality testing, see the [Evaluations Guide](/Guides/evaluations), which uses events internally to run after agent execution. - -## App-Level Events - -App-level events track all agents, sessions, and threads globally. These events fire automatically for all agent executions and lifecycle changes. Register app-level listeners in `app.ts` using `app.addEventListener()`. - -Available app-level events: -- `agent.started`, `agent.completed`, `agent.errored` - Track all agent executions -- `session.started`, `session.completed` - Track session lifecycle -- `thread.created`, `thread.destroyed` - Track thread lifecycle - -**Event Bubbling:** When an agent event fires, both the agent-level listeners and app-level listeners execute. This allows for both specific and global monitoring. - -### Global Agent Tracking - -Track all agent executions across your application: - -```typescript -import { createApp } from '@agentuity/runtime'; - -const app = createApp(); - -// Track execution counts per agent -const executionCounts = new Map(); - -app.addEventListener('agent.started', (eventName, agent, ctx) => { - const agentName = agent.metadata.name || 'unknown'; - const count = executionCounts.get(agentName) || 0; - executionCounts.set(agentName, count + 1); - - app.logger.info('Agent execution started', { - agent: agentName, - executionCount: count + 1, - session: c.sessionId - }); -}); - -app.addEventListener('agent.completed', (eventName, agent, ctx) => { - app.logger.info('Agent execution completed', { - agent: agent.metadata.name, - session: c.sessionId - }); -}); - -app.addEventListener('agent.errored', (eventName, agent, ctx, error) => { - app.logger.error('Agent execution failed', { - agent: agent.metadata.name, - error: error.message, - stack: error.stack, - session: c.sessionId - }); -}); - -export default app.server; -``` - -**Key Points:** -- App-level events provide global visibility into all executions -- Use for cross-cutting concerns like analytics and monitoring -- Event handlers have access to agent metadata and context - -### Session & Thread Lifecycle - -Monitor session and thread creation and completion: - -```typescript -import { createApp } from '@agentuity/runtime'; - -const app = createApp(); - -// Track active sessions -const activeSessions = new Map(); - -app.addEventListener('session.started', (eventName, session) => { - activeSessions.set(session.id, { - startTime: Date.now(), - requestCount: 0 - }); - - app.logger.info('Session started', { - sessionId: session.id, - threadId: session.thread.id - }); -}); - -app.addEventListener('session.completed', (eventName, session) => { - const sessionData = activeSessions.get(session.id); - - if (sessionData) { - const duration = Date.now() - sessionData.startTime; - - app.logger.info('Session completed', { - sessionId: session.id, - duration, - requestCount: sessionData.requestCount - }); - - activeSessions.delete(session.id); - } -}); - -app.addEventListener('thread.created', (eventName, thread) => { - app.logger.info('Thread created', { - threadId: thread.id - }); -}); - -app.addEventListener('thread.destroyed', (eventName, thread) => { - app.logger.info('Thread destroyed', { - threadId: thread.id - }); -}); - -export default app.server; -``` - -**Key Points:** -- Sessions and threads have their own lifecycle events -- Track session duration and request counts -- Threads are destroyed after 1 hour of inactivity - -## Thread & Session Events - -Thread and session instances provide their own event listeners for cleanup and state management. - -**Thread Events:** -- `destroyed` - Fired when `thread.destroy()` is called or thread expires - -**Session Events:** -- `completed` - Fired when session is saved - -Register listeners directly on thread or session instances within agent handlers. - -For detailed session and thread management patterns, see the [Sessions & Threads Guide](/Guides/sessions-threads). - -### Thread Cleanup - -Clean up resources when threads are destroyed: - -```typescript -import { createAgent } from '@agentuity/runtime'; - -const agent = createAgent({ - handler: async (ctx, input) => { - // Register cleanup handler on first access - if (!c.thread.state.has('cleanupRegistered')) { - c.thread.addEventListener('destroyed', (eventName, thread) => { - ctx.logger.info('Cleaning up thread resources', { - threadId: thread.id, - messageCount: thread.state.get('messageCount') || 0 - }); - - // Clean up any thread-specific resources - thread.state.clear(); - }); - - c.thread.state.set('cleanupRegistered', true); - } - - // Track messages in this thread - const messageCount = (c.thread.state.get('messageCount') as number) || 0; - c.thread.state.set('messageCount', messageCount + 1); - - return { processed: true }; - } -}); - -export default agent; -``` - -**Key Points:** -- Thread `destroyed` event fires when thread expires or is manually destroyed -- Use for cleanup of thread-scoped resources -- Access thread state in event listeners - -### Session State Persistence - -Save session state when sessions complete: - -```typescript -import { createAgent } from '@agentuity/runtime'; -import { z } from 'zod'; - -const agent = createAgent({ - schema: { - input: z.object({ action: z.string() }) - }, - handler: async (ctx, input) => { - // Register session completion handler once - if (!c.session.state.has('persistenceRegistered')) { - c.session.addEventListener('completed', async (eventName, session) => { - // Save session metrics to KV storage - const metrics = { - totalRequests: session.state.get('totalRequests') || 0, - lastAction: session.state.get('lastAction'), - duration: Date.now() - (session.state.get('startTime') as number || Date.now()) - }; - - await c.kv.set('session-metrics', session.id, metrics, { - ttl: 86400 // 24 hours - }); - - ctx.logger.info('Session metrics saved', { - sessionId: session.id, - metrics - }); - }); - - c.session.state.set('persistenceRegistered', true); - c.session.state.set('startTime', Date.now()); - } - - // Track session activity - const totalRequests = (c.session.state.get('totalRequests') as number) || 0; - c.session.state.set('totalRequests', totalRequests + 1); - c.session.state.set('lastAction', input.action); - - return { success: true }; - } -}); - -export default agent; -``` - -**Key Points:** -- Session `completed` event fires when session is saved -- Use for persisting session data to storage -- Register event listeners once using a flag in session state - -## Common Patterns - -### Request Timing & Performance Monitoring - -Track agent execution time and identify slow operations: - -```typescript -import { createAgent } from '@agentuity/runtime'; - -const agent = createAgent({ - handler: async (ctx, input) => { - // Process request - await new Promise(resolve => setTimeout(resolve, 1200)); - return { result: 'completed' }; - } -}); - -agent.addEventListener('started', (eventName, agent, ctx) => { - ctx.state.set('performanceStart', performance.now()); -}); - -agent.addEventListener('completed', (eventName, agent, ctx) => { - const startTime = c.state.get('performanceStart') as number; - const duration = performance.now() - startTime; - - // Log slow executions - if (duration > 1000) { - ctx.logger.warn('Slow agent execution detected', { - agentName: agent.metadata.name, - duration, - threshold: 1000, - sessionId: c.sessionId - }); - } - - c.logger.info('Agent execution time', { - agentName: agent.metadata.name, - duration - }); -}); - -export default agent; -``` - -### Error Tracking & Aggregation - -Capture and aggregate errors across all agents: - -```typescript -import { createApp } from '@agentuity/runtime'; - -const app = createApp(); - -// Track error counts by type -const errorCounts = new Map(); - -app.addEventListener('agent.errored', async (eventName, agent, ctx, error) => { - const errorType = error.name || 'UnknownError'; - const count = errorCounts.get(errorType) || 0; - errorCounts.set(errorType, count + 1); - - // Log detailed error information - app.logger.error('Agent error captured', { - agentName: agent.metadata.name, - errorType, - errorMessage: error.message, - errorCount: count + 1, - sessionId: c.sessionId, - stack: error.stack - }); - - // Store error log in KV storage - const errorLog = { - timestamp: new Date().toISOString(), - agentName: agent.metadata.name, - errorType, - message: error.message, - sessionId: c.sessionId - }; - - const errorKey = `error-${Date.now()}-${Math.random().toString(36).slice(2)}`; - await c.kv.set('error-logs', errorKey, errorLog, { - ttl: 604800 // 7 days - }); -}); - -export default app.server; -``` - -### Analytics & Usage Tracking - -Track agent usage patterns and session metrics: - -```typescript -import { createApp } from '@agentuity/runtime'; - -const app = createApp(); - -// Track agent invocations -app.addEventListener('agent.completed', async (eventName, agent, ctx) => { - const agentName = agent.metadata.name || 'unknown'; - const today = new Date().toISOString().split('T')[0]; - const metricsKey = `agent-metrics-${today}-${agentName}`; - - // Get current count - const result = await c.kv.get('analytics', metricsKey); - let count = 0; - - if (result.exists) { - const data = await result.data.json(); - count = data.count || 0; - } - - // Increment and store - await c.kv.set('analytics', metricsKey, { - count: count + 1, - agentName, - date: today - }, { - ttl: 2592000 // 30 days - }); -}); - -// Track session durations -app.addEventListener('session.completed', async (eventName, session) => { - const startTime = session.state.get('sessionStart') as number; - - if (startTime) { - const duration = Date.now() - startTime; - - app.logger.info('Session analytics', { - sessionId: session.id, - duration, - requestCount: session.state.get('requestCount') || 0 - }); - } -}); - -export default app.server; -``` - -### Audit Logging - -Log all agent executions for compliance and debugging: - -```typescript -import { createApp } from '@agentuity/runtime'; - -const app = createApp(); - -app.addEventListener('agent.started', async (eventName, agent, ctx) => { - const auditLog = { - timestamp: new Date().toISOString(), - event: 'agent.started', - agentName: agent.metadata.name, - agentId: agent.metadata.id, - sessionId: c.sessionId, - agentVersion: agent.metadata.version - }; - - // Store in object storage for long-term retention - const logKey = `${auditLog.timestamp}-${c.sessionId}-${agent.metadata.name}.json`; - - await c.objectstore.put('audit-logs', logKey, auditLog, { - contentType: 'application/json', - metadata: { - sessionId: c.sessionId, - agentName: agent.metadata.name, - timestamp: auditLog.timestamp - } - }); -}); - -app.addEventListener('agent.completed', async (eventName, agent, ctx) => { - const auditLog = { - timestamp: new Date().toISOString(), - event: 'agent.completed', - agentName: agent.metadata.name, - sessionId: c.sessionId - // Note: Do not log full input/output for privacy - }; - - const logKey = `${auditLog.timestamp}-${c.sessionId}-${agent.metadata.name}-completed.json`; - - await c.objectstore.put('audit-logs', logKey, auditLog, { - contentType: 'application/json' - }); -}); - -export default app.server; -``` - -**Key Points:** -- Store audit logs in object storage for long-term retention -- Include timestamps, agent metadata, and session IDs -- Avoid logging sensitive data (full input/output) -- Use metadata for filtering and searching - -See the [API Reference](/api-reference#key-value-storage) for complete documentation on storage APIs (KV, Vector, ObjectStore) and streaming. - -## Best Practices - - -**Event Handler Guidelines** - -- **Keep handlers lightweight** - Event listeners should complete quickly to avoid blocking execution -- **Use `c.waitUntil()`** - For non-blocking background work, use `c.waitUntil()` to defer processing until after the response is sent -- **Don't modify request flow** - Event handlers should not throw errors to stop execution or modify the response -- **Sequential execution** - Event listeners execute in registration order, one at a time -- **Error handling** - Errors in event handlers are logged but don't stop execution or affect other listeners -- **Use appropriate scope** - Use app-level events for cross-cutting concerns, agent-level events for agent-specific logic -- **Avoid side effects** - Event handlers should primarily observe and log, not modify state that affects business logic - - -### Using waitUntil in Events - -Use `c.waitUntil()` to perform heavy work without blocking the response: - -```typescript -import { createAgent } from '@agentuity/runtime'; - -const agent = createAgent({ - handler: async (ctx, input) => { - return { result: 'processed' }; - } -}); - -agent.addEventListener('completed', (eventName, agent, ctx) => { - // Use waitUntil for non-blocking background work - c.waitUntil(async () => { - // Simulate sending metrics to external service - await new Promise(resolve => setTimeout(resolve, 500)); - - ctx.logger.info('Metrics sent to external service', { - agentName: agent.metadata.name, - sessionId: c.sessionId - }); - }); - - // This logs immediately - c.logger.info('Agent completed (handler finished)', { - agentName: agent.metadata.name - }); -}); - -export default agent; -``` - -**Key Points:** -- `waitUntil()` executes after the response is sent to the client -- Use for analytics, metrics, or external API calls -- Prevents blocking the user-facing response -- Multiple `waitUntil()` calls can run concurrently - -## Event Execution Order - -Events execute in a predictable sequence: - -1. Agent `started` event fires -2. App `agent.started` event fires -3. Agent handler executes -4. Agent `completed` event fires (or `errored` if exception occurs) -5. App `agent.completed` event fires (or `agent.errored`) - -All event listeners execute sequentially. If an agent-level `completed` listener and an app-level `agent.completed` listener are both registered, the agent-level listener executes first. - -### Event Order Demonstration - -Observe event execution order with timestamps: - -```typescript -import { createAgent } from '@agentuity/runtime'; -import { createApp } from '@agentuity/runtime'; - -// In app.ts -const app = createApp(); - -app.addEventListener('agent.started', (eventName, agent, ctx) => { - console.log(`[${Date.now()}] App: agent.started - ${agent.metadata.name}`); -}); - -app.addEventListener('agent.completed', (eventName, agent, ctx) => { - console.log(`[${Date.now()}] App: agent.completed - ${agent.metadata.name}`); -}); - -export default app.server; - -// In agent.ts -const agent = createAgent({ - handler: async (ctx, input) => { - console.log(`[${Date.now()}] Agent: handler executing`); - await new Promise(resolve => setTimeout(resolve, 100)); - return { result: 'done' }; - } -}); - -agent.addEventListener('started', (eventName, agent, ctx) => { - console.log(`[${Date.now()}] Agent: started event`); -}); - -agent.addEventListener('completed', (eventName, agent, ctx) => { - console.log(`[${Date.now()}] Agent: completed event`); -}); - -export default agent; -``` - -**Expected Output:** -``` -[1234567890] Agent: started event -[1234567891] App: agent.started -[1234567892] Agent: handler executing -[1234567992] Agent: completed event -[1234567993] App: agent.completed -``` - -**Key Points:** -- Agent-level events fire before app-level events -- All listeners execute sequentially (blocking) -- Handler execution occurs between `started` and `completed` events -- Event timing is predictable and deterministic diff --git a/content/v1/archive/introduction.mdx b/content/v1/archive/introduction.mdx deleted file mode 100644 index d4a115de..00000000 --- a/content/v1/archive/introduction.mdx +++ /dev/null @@ -1,45 +0,0 @@ ---- -title: What is Agentuity? -description: Agentuity is rebuilding the cloud for AI Agents ---- - -
- Build agents, not infrastructure -
- -[Agentuity](https://agentuity.com) is a cloud platform designed specifically to make it easy to build, deploy, and operate AI Agents at scale. - -Our mission is to provide a fully agentic infrastructure and tools necessary to build Agents that are fully operated by AI. That means we allow you to: - -- Build agents like you build web apps: define logic, configure routes, and deploy with a single command. -- Focus on agent behavior, while we handle the infrastructure. - - -If you're ready to dive in, skip to [Getting Started](/Introduction/getting-started) to get your first Agent up and running in minutes. - - - -With Agentuity, you or your agents can: - -- Deploy agents with a single command without configuring IAM roles, load balancers, or security groups -- Connect agents to multiple channels (HTTP, WebSocket, Server-Sent Events, email, SMS, cron, and more) -- Build type-safe agents with schema validation and automated evaluation framework -- Access built-in storage (key-value, vector, object, stream) and observability (logging, tracing, metrics) -- Communicate securely between agents to build multi-agent workflows -- Deploy React frontends alongside agents with hot reload and CDN distribution -- Monitor agent performance with real-time analytics, logs, and OpenTelemetry tracing -- Scale automatically from zero to thousands of requests on-demand - -

-We see a near future where Agents are the primary way to build and operate software and where all the infrastructure is built uniquely for them. -

- -## Deployment Options - -Deploy anywhere—from Agentuity's global edge network to your own infrastructure. The same code runs on public cloud, private VPC, on-premises, or edge devices. See the [Architecture overview](/Introduction/architecture#deployment-targets) for more details. - -## Agentuity Platform Overview - -TODO: Add platform overview video - -If you're ready to have your own Agent, [Get Started](/Introduction/getting-started) in just a few minutes. diff --git a/content/v1/archive/routing-triggers.mdx b/content/v1/archive/routing-triggers.mdx deleted file mode 100644 index c62387ba..00000000 --- a/content/v1/archive/routing-triggers.mdx +++ /dev/null @@ -1,492 +0,0 @@ ---- -title: Routing & Triggers -description: Define HTTP endpoints, scheduled jobs, email handlers, SMS receivers, and more - all from within your agent code ---- - -Routes define how your application responds to different HTTP requests and triggers. The SDK provides HTTP routing and specialized routes for email, WebSocket, scheduled jobs, and other event sources. - - -All triggers are defined in your codebase (`route.ts` files) and are automatically discovered by the SDK. This provides: - -- **Version control** - Routes are tracked in git alongside your agent code -- **Type safety** - TypeScript validates route configurations at compile time -- **Code review** - Changes to triggers are visible in pull requests -- **Testability** - Routes can be unit tested like any other code -- **UI visibility** - Configured routes are displayed in the Agentuity dashboard for monitoring -- **Built-in infrastructure** - Routes automatically include load balancing, monitoring, and error tracking - -No manual UI configuration required. Define routes directly in your agent code, deploy, and they're immediately available. - - -## HTTP Routes - -Routes are created using `createRouter()` and map URL patterns to handler functions. - -### Basic Routes - -```typescript -import { createRouter } from '@agentuity/runtime'; - -const router = createRouter(); - -router.get('/', async (c) => { - return c.json({ message: 'Hello' }); -}); - -router.post('/users', async (c) => { - const body = await c.req.json(); - return c.json({ created: true, data: body }); -}); - -export default router; -``` - -### HTTP Methods - -The router supports standard HTTP methods: `get`, `post`, `put`, `patch`, `delete`, `options`. - -```typescript -router.get('/data', async (c) => c.json({ action: 'read' })); -router.post('/data', async (c) => c.json({ action: 'create' })); -router.put('/data', async (c) => c.json({ action: 'update' })); -router.delete('/data', async (c) => c.json({ action: 'delete' })); -``` - -### Route Parameters - -Capture variable segments in URLs using `:paramName`: - -```typescript -// Single parameter -router.get('/users/:id', async (c) => { - const id = c.req.param('id'); - return c.json({ userId: id }); -}); - -// Multiple parameters -router.get('/posts/:year/:month/:slug', async (c) => { - const year = c.req.param('year'); - const month = c.req.param('month'); - const slug = c.req.param('slug'); - return c.json({ year, month, slug }); -}); -``` - -### Query Parameters - -Access query strings using `c.req.query()`: - -```typescript -router.get('/search', async (c) => { - const query = c.req.query('q'); - const page = c.req.query('page') || '1'; - const limit = c.req.query('limit') || '10'; - - return c.json({ query, page, limit }); -}); -// URL: /search?q=hello&page=2 -// Returns: { "query": "hello", "page": "2", "limit": "10" } -``` - -**Route parameters vs query strings:** -- **Route parameters** (`:id`): Part of the URL structure, typically required -- **Query strings** (`?page=2`): Optional filters or settings - -### Calling Agents from Routes - -Routes can invoke agents using the agent registry: - -```typescript -router.post('/process', async (c) => { - const input = await c.req.json(); - - const result = await c.agent.processorAgent.run({ - data: input.data - }); - - return c.json(result); -}); -``` - -### Validation - -Use `zValidator` middleware to validate request input: - -```typescript -import { zValidator } from '@hono/zod-validator'; -import { z } from 'zod'; - -const schema = z.object({ - name: z.string().min(1), - email: z.string().email() -}); - -router.post('/users', - zValidator('json', schema), - async (c) => { - const data = c.req.valid('json'); // Type-safe, validated data - return c.json({ success: true, data }); - } -); -``` - -## Specialized Routes - -Define triggers for email, SMS, scheduled jobs, WebSocket connections, and real-time streams - all from within your agent code. These specialized routes enable your agents to respond to diverse event sources without any UI configuration. - -### Email Routes - -Handle incoming emails sent to a specific address. - -**Signature:** `router.email(address: string, handler: EmailHandler): Router` - -```typescript -router.email('support@example.com', async (email, c) => { - c.logger.info('Email received', { - from: email.fromEmail(), - subject: email.subject() - }); - - const result = await c.agent.emailProcessor.run({ - sender: email.fromEmail() || 'unknown', - content: email.text() || email.html() || '' - }); - - return c.json({ processed: true }); -}); -``` - -**Email object structure:** -```typescript -interface Email { - // Sender information - fromEmail(): string | null; - fromName(): string | null; - - // Recipient information - to(): string | null; // All recipients (comma-separated) - toEmail(): string | null; // First recipient email - toName(): string | null; // First recipient name - - // Message content - subject(): string | null; - text(): string | null; - html(): string | null; - - // Attachments - attachments(): Array<{ - filename: string; - contentType: string; - }>; - - // Metadata - date(): Date | null; - messageId(): string | null; - headers(): Headers; -} -``` - -### WebSocket Routes - -Create WebSocket endpoints for real-time bidirectional communication. - -**Signature:** `router.websocket(path: string, handler: WebSocketHandler): Router` - -```typescript -router.websocket('/chat', (c) => (ws) => { - ws.onOpen((event) => { - c.logger.info('WebSocket connected'); - ws.send(JSON.stringify({ type: 'connected' })); - }); - - ws.onMessage(async (event) => { - const message = JSON.parse(event.data); - - const response = await c.agent.chatAgent.run({ - message: message.text - }); - - ws.send(JSON.stringify({ type: 'response', data: response })); - }); - - ws.onClose((event) => { - c.logger.info('WebSocket disconnected'); - }); -}); -``` - -**WebSocket connection methods:** -- `onOpen(handler)`: Called when connection opens -- `onMessage(handler)`: Called when message received -- `onClose(handler)`: Called when connection closes -- `send(data)`: Send data to client - -### Server-Sent Events (SSE) - -Create SSE endpoints for server-to-client streaming. - -**Signature:** `router.sse(path: string, handler: SSEHandler): Router` - -```typescript -router.sse('/updates', (c) => async (stream) => { - await stream.write({ type: 'connected' }); - - // Stream agent progress updates - const updates = await c.agent.longRunningAgent.run({ task: 'process' }); - - for (const update of updates) { - await stream.write({ - type: 'progress', - data: update - }); - } - - stream.onAbort(() => { - c.logger.info('Client disconnected'); - }); -}); -``` - -**SSE stream methods:** -- `write(data)`: Send data as JSON -- `writeSSE(message)`: Send raw SSE message with event/id -- `onAbort(handler)`: Called when client disconnects -- `close()`: Close the stream - -### Stream Routes - -Create HTTP streaming endpoints for piping data streams. - -**Signature:** `router.stream(path: string, handler: StreamHandler): Router` - -```typescript -router.stream('/data', async (c) => { - const stream = new ReadableStream({ - async start(controller) { - const data = await c.agent.dataGenerator.run({ query: 'all' }); - - for (const chunk of data) { - controller.enqueue( - new TextEncoder().encode(JSON.stringify(chunk) + '\n') - ); - } - - controller.close(); - } - }); - - return stream; -}); -``` - -### Cron Routes - -Schedule recurring jobs using cron syntax. - -**Signature:** `router.cron(schedule: string, handler: CronHandler): Router` - -```typescript -// Run daily at 9am -router.cron('0 9 * * *', async (c) => { - c.logger.info('Running daily report'); - - const report = await c.agent.reportGenerator.run({ - type: 'daily', - date: new Date().toISOString() - }); - - await c.kv.set('reports', `daily-${Date.now()}`, report); - - return c.json({ success: true }); -}); - -// Run every 5 minutes -router.cron('*/5 * * * *', async (c) => { - await c.agent.healthCheck.run({}); - return c.json({ checked: true }); -}); -``` - -**Cron schedule format:** -``` -┌───────────── minute (0-59) -│ ┌───────────── hour (0-23) -│ │ ┌───────────── day of month (1-31) -│ │ │ ┌───────────── month (1-12) -│ │ │ │ ┌───────────── day of week (0-6, Sunday=0) -│ │ │ │ │ -* * * * * -``` - -**More examples:** -- `0 9 * * *` - Daily at 9am -- `*/5 * * * *` - Every 5 minutes -- `0 0 * * 0` - Weekly on Sunday at midnight -- `0 0 1 * *` - Monthly on the 1st at midnight - -### SMS Routes - -Handle incoming SMS messages sent to a specific phone number. - -**Signature:** `router.sms(params: { number: string }, handler: SMSHandler): Router` - -```typescript -router.sms({ number: '+12345678900' }, async (c) => { - const body = await c.req.json(); - - c.logger.info('SMS received', { - from: body.from, - message: body.text - }); - - const response = await c.agent.smsBot.run({ - sender: body.from, - message: body.text - }); - - return c.json({ reply: response }); -}); -``` - -Phone numbers must use E.164 format (e.g., `+12345678900`). - -## Route Organization - -### Agent Route Pattern - -Each agent typically has two files that work together: - -**agent.ts:** -```typescript -import { createAgent } from '@agentuity/runtime'; -import { z } from 'zod'; - -const agent = createAgent({ - schema: { - input: z.object({ message: z.string() }), - output: z.string() - }, - handler: async (ctx, input) => { - return `Received: ${input.message}`; - } -}); - -export default agent; -``` - -**route.ts:** -```typescript -import { createRouter } from '@agentuity/runtime'; -import { zValidator } from '@hono/zod-validator'; -import agent from './agent'; - -const router = createRouter(); - -// GET route for easy testing -router.get('/', async (c) => { - const result = await c.agent.myAgent.run({ message: 'Test' }); - return c.text(result); -}); - -// POST route with validation -router.post('/', - zValidator('json', agent.inputSchema!), - async (c) => { - const data = c.req.valid('json'); - const result = await c.agent.myAgent.run(data); - return c.text(result); - } -); - -export default router; -``` - -The GET route provides an easy way to test the agent in a browser, while the POST route is the primary endpoint with input validation. - -### Request Context - -The context object (`c`) provides access to request data and Agentuity services: - -**Note:** Route handlers use Hono Context (often called `c`), which is different from the AgentContext used in agent handlers. See the [Context Types Guide](/v1/Guides/context-types) for details. - -**Request data:** -```typescript -c.req.json() // Parse JSON body -c.req.text() // Get text body -c.req.param('id') // Get route parameter -c.req.query('page') // Get query string -c.req.header('Auth') // Get request header -``` - -**Responses:** -```typescript -c.json(data) // Send JSON response -c.text(string) // Send text response -c.html(markup) // Send HTML response -c.redirect(url) // Redirect to URL -``` - -**Agentuity services:** -```typescript -c.agent.name.run() // Call another agent -c.kv.get/set/delete() // Key-value storage -c.vector.search() // Vector database -c.logger.info() // Structured logging -c.objectstore.* // Object storage -``` - -## Best Practices - -### Validate All Input - -Always validate request input using schemas: - -```typescript -// Define schema -const schema = z.object({ - email: z.string().email(), - age: z.number().min(0) -}); - -// Use in route -router.post('/endpoint', - zValidator('json', schema), - async (c) => { - const data = c.req.valid('json'); // Guaranteed valid - // ... - } -); -``` - -### Use Structured Logging - -Use `c.logger` (not `console.log`) for searchable, traceable logs: - -```typescript -c.logger.info('User created', { userId, timestamp: Date.now() }); -c.logger.error('Failed to process', { error: err.message }); -``` - -### Export Router as Default - -Route files must export the router as the default export: - -```typescript -const router = createRouter(); -// ... define routes ... -export default router; -``` - -### Route Order Matters - -Register specific routes before generic ones: - -```typescript -// Correct order -router.get('/users/admin', adminHandler); -router.get('/users/:id', userHandler); - -// Wrong - :id matches everything including "admin" -router.get('/users/:id', userHandler); -router.get('/users/admin', adminHandler); // Never reached -``` diff --git a/content/v1/archive/schema-validation.mdx b/content/v1/archive/schema-validation.mdx deleted file mode 100644 index 29ce7978..00000000 --- a/content/v1/archive/schema-validation.mdx +++ /dev/null @@ -1,711 +0,0 @@ ---- -title: Schema Validation -description: Type-safe input and output validation for agents ---- - -Schema validation provides runtime type safety and automatic validation for your agents. By defining schemas, you ensure data integrity, get full TypeScript autocomplete, and catch errors before they reach production. - -## Why Use Schema Validation - -**Runtime Safety:** -- Validates input before your handler runs -- Validates output before returning to callers -- Prevents invalid data from causing runtime errors - -**Type Safety:** -- Full TypeScript autocomplete in your IDE -- Compile-time type checking -- No need to manually define TypeScript interfaces - -**Documentation:** -- Schemas serve as clear documentation of expected inputs and outputs -- Self-documenting API contracts - -## StandardSchema - -The SDK uses the StandardSchemaV1 interface, which allows different validation libraries to work seamlessly together. - -### Supported Libraries - -**Zod** - Most popular, and most common in Agentuity examples: -```typescript -import { z } from 'zod'; -``` - -**Valibot** - Lightweight alternative with similar API: -```typescript -import * as v from 'valibot'; -``` - -**ArkType** - TypeScript-first validation: -```typescript -import { type } from 'arktype'; -``` - - -Any library implementing StandardSchemaV1 will work with the SDK. - - -### Why StandardSchema? - -StandardSchemaV1 is a common interface that provides: -- **Library independence** - Not locked into a single validation library -- **Flexibility** - Use the library that best fits your needs -- **Future-proof** - New libraries work automatically if they implement the standard - -## Defining Schemas - -Schemas are defined in the `schema` property of `createAgent()`. - -### Input Schemas - -Input schemas validate data passed to your agent: - -```typescript -import { createAgent } from '@agentuity/runtime'; -import { z } from 'zod'; - -const agent = createAgent({ - schema: { - input: z.object({ - email: z.string().email(), - age: z.number().min(0).max(120), - preferences: z.object({ - newsletter: z.boolean(), - notifications: z.boolean(), - }).optional(), - }), - }, - handler: async (ctx, input) => { - // input is validated before this runs - // TypeScript knows the exact shape of input - ctx.logger.info(`User email: ${input.email}`); - return { success: true }; - }, -}); -``` - -### Output Schemas - -Output schemas validate data returned from your agent: - -```typescript -const agent = createAgent({ - schema: { - output: z.object({ - userId: z.string().uuid(), - created: z.date(), - status: z.enum(['active', 'pending']), - }), - }, - handler: async (ctx, input) => { - return { - userId: crypto.randomUUID(), - created: new Date(), - status: 'active' - }; - // Output is validated before returning to caller - }, -}); -``` - -### Combined Input and Output - -Define both input and output schemas for complete validation: - -```typescript -const agent = createAgent({ - schema: { - input: z.object({ - query: z.string(), - limit: z.number().default(10), - }), - output: z.object({ - results: z.array(z.string()), - total: z.number(), - }), - }, - handler: async (ctx, input) => { - // Both input and output are validated - return { - results: ['item1', 'item2'], - total: 2, - }; - }, -}); -``` - -### Optional Schemas - -Schemas are optional. If not provided, no validation occurs: - -```typescript -const agent = createAgent({ - // No schema defined - handler: async (ctx, input) => { - // input is unknown type - // No runtime validation - return { message: 'Done' }; - }, -}); -``` - -For type safety, it's recommended to add a schema. - -## Type Inference - -TypeScript automatically infers types from your schemas, providing full autocomplete and compile-time type checking. - -### Inferred Input Types - -```typescript -const agent = createAgent({ - schema: { - input: z.object({ - query: z.string(), - filters: z.object({ - category: z.enum(['tech', 'business', 'sports']), - limit: z.number().default(10), - }), - }), - }, - handler: async (ctx, input) => { - // TypeScript knows: - // - input.query is string - // - input.filters.category is 'tech' | 'business' | 'sports' - // - input.filters.limit is number - - const category = input.filters.category; // Full autocomplete! - return { processed: true }; - }, -}); -``` - -### Inferred Output Types - -```typescript -const agent = createAgent({ - schema: { - output: z.object({ - results: z.array(z.object({ - id: z.string(), - title: z.string(), - score: z.number(), - })), - total: z.number(), - }), - }, - handler: async (ctx, input) => { - // Return type is validated and type-checked - return { - results: [ - { id: '1', title: 'Example', score: 0.95 }, - ], - total: 1, - }; - - // This would cause a TypeScript error: - // return { invalid: 'structure' }; - }, -}); -``` - -### Calling Agents with Type Inference - -When calling agents, TypeScript knows the input and output types: - -```typescript -// When calling the agent from another agent: -const result = await ctx.agent.searchAgent.run({ - query: 'agentic AI', - filters: { category: 'tech', limit: 5 }, -}); - -// TypeScript knows result has this shape: -// { -// results: Array<{ id: string; title: string; score: number }>; -// total: number; -// } - -console.log(result.results[0].title); // Full autocomplete! -``` - -**Benefits:** -- Full IDE autocomplete for input and output -- Compile-time type checking catches errors before runtime -- No need to manually define TypeScript interfaces -- Refactoring is safer - changes to schemas update types automatically - -## Validation Libraries - -### Zod - -[Zod](https://zod.dev/) is the most popular validation library with excellent TypeScript support. - -```typescript -import { z } from 'zod'; - -const schema = z.object({ - name: z.string().min(1), - email: z.string().email(), - age: z.number().min(18), - role: z.enum(['admin', 'user', 'guest']), - tags: z.array(z.string()).optional(), -}); -``` - -**Common Zod patterns:** -```typescript -// String validation -z.string() // Any string -z.string().min(5) // Minimum length -z.string().max(100) // Maximum length -z.string().email() // Email format -z.string().url() // URL format -z.string().uuid() // UUID format - -// Number validation -z.number() // Any number -z.number().min(0) // Minimum value -z.number().max(100) // Maximum value -z.number().int() // Integer only -z.number().positive() // Positive numbers - -// Boolean, null, undefined -z.boolean() // Boolean -z.null() // Null -z.undefined() // Undefined - -// Arrays and objects -z.array(z.string()) // Array of strings -z.object({ name: z.string() }) // Object shape -z.record(z.string()) // Record/map - -// Optional and nullable -z.string().optional() // string | undefined -z.string().nullable() // string | null -z.string().nullish() // string | null | undefined - -// Defaults -z.string().default('hello') // Use default if undefined -z.number().default(0) // Use default if undefined - -// Enums and literals -z.enum(['a', 'b', 'c']) // One of these strings -z.literal('exact') // Exact value -z.union([z.string(), z.number()]) // Multiple types -``` - -### Valibot - -[Valibot](https://valibot.dev/) is a lightweight alternative with a similar API. - -```typescript -import * as v from 'valibot'; - -const schema = v.object({ - name: v.string([v.minLength(1)]), - email: v.string([v.email()]), - age: v.number([v.minValue(18)]), - role: v.union([ - v.literal('admin'), - v.literal('user'), - v.literal('guest') - ]), -}); -``` - -**Common Valibot patterns:** -```typescript -// String validation -v.string() // Any string -v.string([v.minLength(5)]) // Minimum length -v.string([v.maxLength(100)]) // Maximum length -v.string([v.email()]) // Email format -v.string([v.url()]) // URL format - -// Number validation -v.number() // Any number -v.number([v.minValue(0)]) // Minimum value -v.number([v.maxValue(100)]) // Maximum value -v.number([v.integer()]) // Integer only - -// Arrays and objects -v.array(v.string()) // Array of strings -v.object({ name: v.string() }) // Object shape - -// Optional and nullable -v.optional(v.string()) // string | undefined -v.nullable(v.string()) // string | null -v.nullish(v.string()) // string | null | undefined -``` - -### ArkType - -[ArkType](https://arktype.io/) provides a TypeScript-first approach to validation. - -```typescript -import { type } from 'arktype'; - -const schema = type({ - name: 'string>0', - email: 'string.email', - age: 'number>=18', - role: '"admin"|"user"|"guest"', - tags: 'string[]?', -}); -``` - -**Common ArkType patterns:** -```typescript -// String validation -type('string') // Any string -type('string>5') // Minimum length -type('string<100') // Maximum length -type('string.email') // Email format -type('string.url') // URL format - -// Number validation -type('number') // Any number -type('number>0') // Greater than 0 -type('number<=100') // Less than or equal to 100 -type('integer') // Integer only - -// Arrays and objects -type('string[]') // Array of strings -type({ name: 'string' }) // Object shape - -// Optional -type('string?') // string | undefined -type('string|null') // string | null -``` - -## Validation in Agents - -### Complete Example - -```typescript -import { createAgent } from '@agentuity/runtime'; -import { z } from 'zod'; - -const agent = createAgent({ - schema: { - input: z.object({ - query: z.string().min(1), - options: z.object({ - limit: z.number().default(10), - includeMetadata: z.boolean().default(false), - }).optional(), - }), - output: z.object({ - results: z.array(z.object({ - id: z.string(), - title: z.string(), - score: z.number().min(0).max(1), - })), - count: z.number(), - }), - }, - handler: async (ctx, input) => { - // Input validated before handler runs - ctx.logger.info('Processing query', { query: input.query }); - - // Process the query... - const results = [ - { id: '1', title: 'Result 1', score: 0.95 }, - { id: '2', title: 'Result 2', score: 0.87 }, - ]; - - // Output validated before returning - return { - results, - count: results.length, - }; - }, -}); - -export default agent; -``` - -### Validation Behavior - -**Input validation:** -- Runs automatically before handler execution -- If validation fails, an error is thrown and the handler is not called -- Error includes detailed information about what failed - -**Output validation:** -- Runs automatically after handler execution -- If validation fails, an error is thrown before returning to the caller -- Ensures consistent output shape - -## Validation in Routes - -Schemas can be used in route handlers with the `zValidator` middleware. - -### Using zValidator - -```typescript -import { createRouter } from '@agentuity/runtime'; -import { zValidator } from '@hono/zod-validator'; -import agent from './agent'; - -const router = createRouter(); - -router.post('/', - zValidator('json', agent.inputSchema!), - async (c) => { - const data = c.req.valid('json'); // Type-safe, validated data - const result = await c.agent.myAgent.run(data); - return c.json(result); - } -); - -export default router; -``` - -### Route vs Agent Validation - -**Route validation** (with zValidator): -- Validates HTTP request body before reaching your handler -- Returns 400 error if validation fails -- Useful for public-facing endpoints - -**Agent validation** (with schema): -- Validates input when agent is called (from routes or other agents) -- Validates output before returning -- Ensures consistency across all callers - -Both can be used together for defense in depth. - -## Error Handling - -### Validation Failures - -When validation fails, an error is thrown with detailed information: - -```typescript -try { - await c.agent.userAgent.run({ - email: 'invalid-email', // Invalid format - age: -5, // Invalid range - }); -} catch (error) { - // Error message includes: - // - Which field failed validation - // - What the validation rule was - // - What the actual value was - console.error('Validation error:', error.message); -} -``` - -### Input Validation Errors - -If input validation fails, the handler never runs: - -```typescript -const agent = createAgent({ - schema: { - input: z.object({ age: z.number().min(0) }), - }, - handler: async (ctx, input) => { - // This never runs if age is negative - return { processed: true }; - }, -}); - -// This throws a validation error: -await ctx.agent.myAgent.run({ age: -1 }); -``` - -### Output Validation Errors - -If output validation fails, the error is thrown before returning: - -```typescript -const agent = createAgent({ - schema: { - output: z.object({ status: z.enum(['ok', 'error']) }), - }, - handler: async (ctx, input) => { - return { status: 'invalid' }; // Validation error! - }, -}); -``` - -## Advanced Patterns - -### Optional Fields - -Use `.optional()` for fields that may be omitted: - -```typescript -z.object({ - required: z.string(), - optional: z.string().optional(), - withDefault: z.number().default(10), -}) - -// Valid inputs: -{ required: 'value' } -{ required: 'value', optional: 'text' } -{ required: 'value', optional: 'text', withDefault: 20 } -``` - -### Nested Objects - -Define complex nested structures: - -```typescript -z.object({ - user: z.object({ - profile: z.object({ - name: z.string(), - avatar: z.string().url().optional(), - }), - settings: z.object({ - theme: z.enum(['light', 'dark']), - notifications: z.boolean(), - }), - }), -}) -``` - -### Arrays and Unions - -Validate arrays and multiple types: - -```typescript -z.object({ - tags: z.array(z.string()), // Array of strings - ids: z.array(z.number()).min(1).max(10), // 1-10 numbers - status: z.union([ // Multiple types - z.literal('active'), - z.literal('inactive'), - z.literal('pending') - ]), - value: z.string().or(z.number()), // String or number -}) -``` - -### Transformations - -Transform data during validation: - -```typescript -z.object({ - email: z.string().email().toLowerCase(), // Convert to lowercase - name: z.string().trim(), // Trim whitespace - age: z.string().transform(val => parseInt(val)), // Parse to number -}) -``` - -### Custom Validation - -Add custom validation logic: - -```typescript -z.object({ - password: z.string() - .min(8) - .refine(val => /[A-Z]/.test(val), { - message: 'Password must contain uppercase letter' - }), - - startDate: z.date(), - endDate: z.date(), -}).refine(data => data.endDate > data.startDate, { - message: 'End date must be after start date' -}); -``` - -## Best Practices - -### Always Validate Input - -Validate input for all agents to prevent runtime errors: - -```typescript -// Good - validated input -const agent = createAgent({ - schema: { - input: z.object({ - email: z.string().email(), - age: z.number().min(0) - }) - }, - handler: async (ctx, input) => { - // input is guaranteed valid - }, -}); -``` - -### Use Schemas as Documentation - -Schemas serve as clear documentation of your agent's API: - -```typescript -schema: { - input: z.object({ - // Query string to search for - query: z.string().min(1).max(500), - - // Optional filters - filters: z.object({ - category: z.enum(['tech', 'business', 'sports']), - dateRange: z.object({ - from: z.date(), - to: z.date(), - }).optional(), - }).optional(), - - // Maximum results (1-100) - limit: z.number().min(1).max(100).default(10), - }), -} -``` - -### Choose the Right Library - -**Zod** - Best for most projects: -- Most popular with excellent community support -- Great TypeScript integration -- Comprehensive validation features - -**Valibot** - Best for bundle size: -- Smaller bundle than Zod -- Similar API and features -- Good for client-side applications - -**ArkType** - Best for TypeScript-first teams: -- Concise type-first syntax -- Strong TypeScript inference -- Good for complex type scenarios - -### Validate Output for Consistency - -Define output schemas to ensure consistent responses: - -```typescript -schema: { - output: z.object({ - success: z.boolean(), - data: z.any().optional(), - error: z.string().optional(), - }) -} -``` - -This ensures all code paths return the expected shape. - -### Use Strict Mode - -Enable strict mode to catch common mistakes: - -```typescript -z.object({ - name: z.string() -}).strict() // Rejects unknown keys -``` - -This prevents accidentally passing extra fields that might indicate bugs. diff --git a/content/v1/archive/sessions-threads.mdx b/content/v1/archive/sessions-threads.mdx deleted file mode 100644 index 584c3672..00000000 --- a/content/v1/archive/sessions-threads.mdx +++ /dev/null @@ -1,880 +0,0 @@ ---- -title: Sessions & Threads -description: Stateful context management for conversational agents ---- - -Sessions and threads provide stateful context management at three different scopes: request-level for temporary data, thread-level for conversation context lasting up to 1 hour, and session-level for user data spanning multiple conversations. Use request state (`ctx.state`) for timing and calculations, thread state (`ctx.thread.state`) for chatbot memory, and session state (`ctx.session.state`) for user preferences and cross-conversation tracking. - -For event-based lifecycle management of sessions and threads, see the [Events Guide](/Guides/events). - -## Understanding the Three Scopes - -The SDK provides three distinct state scopes, each with different lifetimes and use cases: - -- **Request state** (`ctx.state`) - Temporary data within a single request, cleared after response -- **Thread state** (`ctx.thread.state`) - Conversation context across multiple requests, expires after 1 hour -- **Session state** (`ctx.session.state`) - User-level data spanning multiple threads and conversations - -### Three State Scopes in Action - -This example demonstrates all three scopes and when each is used: - -```typescript -import { createAgent } from '@agentuity/runtime'; -import { z } from 'zod'; - -const agent = createAgent({ - schema: { - input: z.object({ message: z.string() }), - output: z.object({ - response: z.string(), - requestTime: z.number(), - conversationLength: z.number(), - totalUserRequests: z.number() - }) - }, - handler: async (ctx, input) => { - // REQUEST STATE: Temporary data for this request only - ctx.state.set('requestStart', Date.now()); - - // THREAD STATE: Conversation history (persists up to 1 hour) - const messages = (c.thread.state.get('messages') as string[]) || []; - messages.push(input.message); - ctx.thread.state.set('messages', messages); - - // SESSION STATE: User-level data (persists across threads) - const totalRequests = (c.session.state.get('totalRequests') as number) || 0; - ctx.session.state.set('totalRequests', totalRequests + 1); - ctx.session.state.set('lastMessage', input.message); - - // Request state is used for response calculation - const requestTime = Date.now() - (c.state.get('requestStart') as number); - - return { - response: `Received: ${input.message}`, - requestTime, - conversationLength: messages.length, - totalUserRequests: totalRequests + 1 - }; - // REQUEST STATE cleared after this return - // THREAD STATE persists for up to 1 hour - // SESSION STATE persists indefinitely (in memory) - } -}); - -export default agent; -``` - -**Key Points:** -- Request state exists only during handler execution -- Thread state maintains conversation context -- Session state tracks user-level metrics across conversations - -### State Scope Comparison - -| Scope | Lifetime | Cleared When | Use Case | Access | -|-------|----------|--------------|----------|--------| -| Request | Single request | After response sent | Timing, temp calculations | `ctx.state` | -| Thread | Up to 1 hour | Thread expiration or destroy | Conversation history | `ctx.thread.state` | -| Session | Spans threads | In-memory (provider dependent) | User preferences | `c.session.state` | - -## Thread Management - -Threads represent conversation contexts with a 1-hour lifetime. Each thread is identified by a unique thread ID stored in a cookie, enabling conversation continuity across multiple requests. - -### Thread Lifecycle - -**Creation:** -- Threads are created automatically on the first request from a client -- Thread ID (format: `thrd_`) is stored in the `atid` cookie -- Cookie enables thread restoration on subsequent requests - -**Restoration:** -- If the `atid` cookie is present, the existing thread is restored -- Thread state is maintained across requests from the same client - -**Expiration:** -- Threads expire after 1 hour (3600000ms) of inactivity -- Expired threads are automatically cleaned up every 60 seconds -- `destroyed` event fires when thread expires - -**Manual Destroy:** -- Call `await c.thread.destroy()` to reset the conversation -- Useful for starting new conversations or clearing context - -### Conversation Context with Threads - -Load conversation from KV storage and cache in thread state for fast access: - -```typescript -import { createAgent } from '@agentuity/runtime'; -import { z } from 'zod'; - -const agent = createAgent({ - schema: { - input: z.object({ - message: z.string(), - reset: z.boolean().optional() - }), - output: z.object({ - response: z.string(), - messageCount: z.number(), - source: z.enum(['kv', 'thread-state', 'new']) - }) - }, - handler: async (ctx, input) => { - // Reset conversation if requested - if (input.reset) { - await c.thread.destroy(); - } - - const conversationKey = `conversation_${c.thread.id}`; - let source: 'kv' | 'thread-state' | 'new' = 'new'; - - // Load conversation from KV on first access - if (!c.thread.state.has('messages')) { - const result = await c.kv.get('conversations', conversationKey); - - if (result.exists) { - const saved = await result.data.json(); - ctx.thread.state.set('messages', saved.messages); - source = 'kv'; - ctx.logger.info('Loaded conversation from KV', { - threadId: c.thread.id, - messageCount: saved.messages.length - }); - } else { - ctx.thread.state.set('messages', []); - } - - // Register save handler when thread is destroyed - ctx.thread.addEventListener('destroyed', async (eventName, thread) => { - const messages = thread.state.get('messages') as string[]; - - if (messages && messages.length > 0) { - await c.kv.set('conversations', conversationKey, { - threadId: thread.id, - messages, - savedAt: new Date().toISOString() - }, { - ttl: 86400 // Keep for 24 hours - }); - - ctx.logger.info('Saved conversation to KV', { - threadId: thread.id, - messageCount: messages.length - }); - } - }); - } else { - source = 'thread-state'; - } - - // Add message to thread state (fast access) - const messages = c.thread.state.get('messages') as string[]; - messages.push(input.message); - ctx.thread.state.set('messages', messages); - - return { - response: `Stored message ${messages.length}`, - messageCount: messages.length, - source - }; - } -}); - -export default agent; -``` - -**Key Points:** -- Load from KV on first access, cache in thread.state -- Thread state provides fast access during thread lifetime -- Save to KV when thread is destroyed for persistence -- Source field shows where data came from (KV, thread-state, or new) - -## Session Management - -Sessions represent individual request executions with unique session IDs. While threads track conversations, sessions track user-level data that spans multiple threads. - -### Session Lifecycle - -**Creation:** -- A new session is created for each request -- Each session has a unique `sessionId` accessible via `c.sessionId` -- Sessions belong to a thread: `c.session.thread` - -**State:** -- `c.session.state` persists across threads for the same user -- Use for user preferences, settings, cross-conversation data -- State stored in memory (persistence depends on SessionProvider) - -**Completion:** -- Sessions complete at the end of each request -- `session.completed` event fires on completion -- Use event for persisting data to storage - -## Thread Expiration & Cleanup - -Threads automatically expire after 1 hour of inactivity. The cleanup process runs every 60 seconds to remove expired threads. - -### Thread Cleanup on Expiration - -Clean up resources when threads are destroyed (either by expiration or manual destroy): - -```typescript -import { createAgent } from '@agentuity/runtime'; - -const agent = createAgent({ - handler: async (ctx, input) => { - // Register cleanup handler once per thread - if (!c.thread.state.has('cleanupRegistered')) { - ctx.thread.addEventListener('destroyed', async (eventName, thread) => { - ctx.logger.info('Thread destroyed - cleaning up', { - threadId: thread.id, - messageCount: thread.state.get('messageCount') || 0, - conversationDuration: Date.now() - (thread.state.get('startTime') as number || Date.now()) - }); - - // Save conversation summary to KV before cleanup - const messages = thread.state.get('messages') as string[] || []; - if (messages.length > 0) { - await c.kv.set('conversation-summaries', thread.id, { - messageCount: messages.length, - lastMessages: messages.slice(-5), - endedAt: new Date().toISOString() - }, { - ttl: 86400 // Keep summary for 24 hours - }); - } - - // Clear thread state - thread.state.clear(); - }); - - ctx.thread.state.set('cleanupRegistered', true); - ctx.thread.state.set('startTime', Date.now()); - } - - // Track messages - const messageCount = (c.thread.state.get('messageCount') as number) || 0; - ctx.thread.state.set('messageCount', messageCount + 1); - - const messages = (c.thread.state.get('messages') as string[]) || []; - messages.push(JSON.stringify(input)); - ctx.thread.state.set('messages', messages); - - return { processed: true, messageCount: messageCount + 1 }; - } -}); - -export default agent; -``` - -**Key Points:** -- Threads expire after 1 hour of inactivity -- `destroyed` event fires on expiration or manual destroy -- Use event for cleanup and saving important data -- Register event listener once per thread - -## Common Patterns - -### Chatbot with Conversation Memory - -Production-ready chatbot with LLM integration and conversation persistence: - - -This example requires the Vercel AI SDK. Install with: -```bash -npm install ai @ai-sdk/openai -``` - - -```typescript -import { createAgent } from '@agentuity/runtime'; -import { z } from 'zod'; -import { streamText } from 'ai'; -import { openai } from '@ai-sdk/openai'; -import type { CoreMessage } from 'ai'; - -const agent = createAgent({ - schema: { - input: z.object({ - message: z.string() - }), - stream: true - }, - handler: async (ctx, input) => { - const conversationKey = `chat_${c.thread.id}`; - - // Load conversation history from KV - let messages: CoreMessage[] = []; - - try { - const result = await c.kv.get('conversations', conversationKey); - - if (result.exists) { - messages = await result.data.json() as CoreMessage[]; - ctx.logger.info('Loaded conversation from KV', { - threadId: c.thread.id, - messageCount: messages.length - }); - } - } catch (error) { - ctx.logger.error('Error loading conversation history', { error }); - } - - // Add user message to conversation - messages.push({ - role: 'user', - content: input.message - }); - - // Stream LLM response with conversation context - const result = streamText({ - model: openai('gpt-4o-mini'), - system: 'You are a helpful assistant. Keep responses concise.', - messages - }); - - // Save conversation after stream completes (non-blocking) - c.waitUntil(async () => { - try { - const fullText = await result.text; - - // Add assistant response to messages - messages.push({ - role: 'assistant', - content: fullText - }); - - // Keep last 20 messages (10 turns) to manage state size - const recentMessages = messages.slice(-20); - - // Save to KV storage - await c.kv.set('conversations', conversationKey, recentMessages, { - ttl: 86400 // 24 hours - }); - - ctx.logger.info('Saved conversation to KV', { - threadId: c.thread.id, - messageCount: recentMessages.length - }); - } catch (error) { - ctx.logger.error('Error saving conversation history', { error }); - } - }); - - // Return stream to client - return result.textStream; - } -}); - -export default agent; -``` - -**Key Points:** -- LLM integration with conversation context -- Load conversation from KV using thread ID as key -- Stream response to client for better UX -- Use `waitUntil()` to save asynchronously after stream completes -- Limit conversation to last 20 messages to manage memory -- Error handling for KV operations - -### User Preferences with Persistence - -Load, cache, and persist user preferences using session state and KV storage: - -```typescript -import { createAgent } from '@agentuity/runtime'; -import { z } from 'zod'; - -const PreferencesSchema = z.object({ - language: z.string(), - theme: z.enum(['light', 'dark']), - notifications: z.boolean(), - timezone: z.string() -}); - -const agent = createAgent({ - schema: { - input: z.object({ - userId: z.string(), - updatePreferences: PreferencesSchema.partial().optional() - }), - output: z.object({ - preferences: PreferencesSchema, - source: z.enum(['cache', 'storage', 'default']) - }) - }, - handler: async (ctx, input) => { - const userId = input.userId; - let source: 'cache' | 'storage' | 'default' = 'default'; - - // Check session state cache first - let preferences = c.session.state.get(`preferences_${userId}`) as z.infer | undefined; - - if (preferences) { - source = 'cache'; - } else { - // Load from KV storage - const result = await c.kv.get('user-preferences', userId); - - if (result.exists) { - preferences = await result.data.json(); - source = 'storage'; - } else { - // Use defaults - preferences = { - language: 'en', - theme: 'light', - notifications: true, - timezone: 'UTC' - }; - } - - // Cache in session state - ctx.session.state.set(`preferences_${userId}`, preferences); - } - - // Update if requested - if (input.updatePreferences) { - preferences = { ...preferences, ...input.updatePreferences }; - ctx.session.state.set(`preferences_${userId}`, preferences); - - // Persist to KV storage - await c.kv.set('user-preferences', userId, preferences, { - ttl: 2592000 // 30 days - }); - } - - return { preferences, source }; - } -}); - -export default agent; -``` - -**Key Points:** -- Load from KV storage first, cache in session state -- Session state provides fast access across requests -- Persist updates back to KV for durability -- `source` field shows data origin (cache, storage, or default) - -### Multi-Turn Workflow State - -Track workflow progress across multiple requests: - -```typescript -import { createAgent } from '@agentuity/runtime'; -import { z } from 'zod'; - -const WorkflowStateSchema = z.object({ - step: z.enum(['start', 'collectName', 'collectEmail', 'confirm', 'complete']), - data: z.object({ - name: z.string().optional(), - email: z.string().optional() - }) -}); - -const agent = createAgent({ - schema: { - input: z.object({ - value: z.string().optional(), - reset: z.boolean().optional() - }), - output: z.object({ - message: z.string(), - step: z.string(), - complete: z.boolean() - }) - }, - handler: async (ctx, input) => { - // Reset workflow if requested - if (input.reset) { - ctx.thread.state.delete('workflowState'); - } - - // Initialize workflow - let workflowState = c.thread.state.get('workflowState') as z.infer | undefined; - - if (!workflowState) { - workflowState = { - step: 'start', - data: {} - }; - } - - let message = ''; - let complete = false; - - // Process based on current step - switch (workflowState.step) { - case 'start': - message = 'Welcome! Please provide your name.'; - workflowState.step = 'collectName'; - break; - - case 'collectName': - if (input.value) { - workflowState.data.name = input.value; - workflowState.step = 'collectEmail'; - message = `Hello ${input.value}! Please provide your email.`; - } else { - message = 'Please provide your name.'; - } - break; - - case 'collectEmail': - if (input.value) { - workflowState.data.email = input.value; - workflowState.step = 'confirm'; - message = `Please confirm: Name: ${workflowState.data.name}, Email: ${input.value}. Reply 'yes' to confirm.`; - } else { - message = 'Please provide your email.'; - } - break; - - case 'confirm': - if (input.value?.toLowerCase() === 'yes') { - workflowState.step = 'complete'; - message = 'Registration complete!'; - complete = true; - - // Save to storage - await c.kv.set('registrations', workflowState.data.email!, workflowState.data, { - ttl: 86400 - }); - } else { - message = 'Please reply "yes" to confirm or start over.'; - } - break; - - case 'complete': - message = 'Workflow already complete. Use reset=true to start over.'; - complete = true; - break; - } - - // Save workflow state - ctx.thread.state.set('workflowState', workflowState); - - return { - message, - step: workflowState.step, - complete - }; - } -}); - -export default agent; -``` - -### Session-Based Rate Limiting - -Implement rate limiting per session: - -```typescript -import { createAgent } from '@agentuity/runtime'; - -const agent = createAgent({ - handler: async (ctx, input) => { - const requestLimit = 10; - const windowMs = 60000; // 1 minute - - // Track requests in session state - const requestLog = (c.session.state.get('requestLog') as Array) || []; - const now = Date.now(); - - // Remove requests outside the time window - const recentRequests = requestLog.filter(timestamp => now - timestamp < windowMs); - - // Check rate limit - if (recentRequests.length >= requestLimit) { - const oldestRequest = Math.min(...recentRequests); - const resetIn = windowMs - (now - oldestRequest); - - ctx.logger.warn('Rate limit exceeded', { - sessionId: c.sessionId, - requestCount: recentRequests.length, - resetInMs: resetIn - }); - - return { - error: 'Rate limit exceeded', - resetInSeconds: Math.ceil(resetIn / 1000), - requestCount: recentRequests.length, - limit: requestLimit - }; - } - - // Add current request - recentRequests.push(now); - ctx.session.state.set('requestLog', recentRequests); - - return { - processed: true, - requestCount: recentRequests.length, - remainingRequests: requestLimit - recentRequests.length - }; - } -}); - -export default agent; -``` - -## State Persistence - -In-memory state is ephemeral and lost on server restarts. For durable data, use KV storage in combination with thread and session state. - -**Pattern:** -1. Load data from KV storage -2. Cache in thread/session state for fast access -3. Update state during request processing -4. Save back to KV on thread destroy or session complete - -### Session-Level Data Persistence - -Persist user profile data across sessions: - -```typescript -import { createAgent } from '@agentuity/runtime'; -import { z } from 'zod'; - -const UserProfileSchema = z.object({ - userId: z.string(), - name: z.string(), - preferences: z.object({ - language: z.string(), - timezone: z.string() - }), - lastActive: z.string() -}); - -const agent = createAgent({ - schema: { - input: z.object({ - userId: z.string(), - updateProfile: UserProfileSchema.partial().optional() - }), - output: z.object({ - profile: UserProfileSchema - }) - }, - handler: async (ctx, input) => { - const userId = input.userId; - - // Load profile from session state cache - let profile = c.session.state.get(`profile_${userId}`) as z.infer | undefined; - - // If not in cache, load from KV - if (!profile) { - const result = await c.kv.get('user-profiles', userId); - - if (result.exists) { - profile = await result.data.json(); - } else { - // Create default profile - profile = { - userId, - name: 'Guest', - preferences: { - language: 'en', - timezone: 'UTC' - }, - lastActive: new Date().toISOString() - }; - } - - // Cache in session state - ctx.session.state.set(`profile_${userId}`, profile); - - // Register session completion handler to save on exit - if (!c.session.state.has('saveHandlerRegistered')) { - ctx.session.addEventListener('completed', async (eventName, session) => { - const profileToSave = session.state.get(`profile_${userId}`) as typeof profile; - - if (profileToSave) { - await c.kv.set('user-profiles', userId, profileToSave, { - ttl: 2592000 // 30 days - }); - - ctx.logger.info('Saved user profile', { userId }); - } - }); - - ctx.session.state.set('saveHandlerRegistered', true); - } - } - - // Update profile if requested - if (input.updateProfile) { - profile = { ...profile, ...input.updateProfile }; - profile.lastActive = new Date().toISOString(); - ctx.session.state.set(`profile_${userId}`, profile); - } - - return { profile }; - } -}); - -export default agent; -``` - -## Best Practices - - -**State Management Guidelines** - -- **Use the right scope** - Request state for temp data, thread state for conversations, session state for user data -- **Keep state size manageable** - Limit conversation history, avoid large objects in memory -- **Persist important data** - Use KV storage for data that must survive restarts -- **Clean up resources** - Register cleanup handlers in `destroyed` and `completed` events -- **Cache strategically** - Load from KV once, cache in state, save on completion -- **Don't rely on state for critical data** - In-memory state can be lost on deployment or restart -- **Use thread.destroy() for resets** - Don't use it for cleanup (use events instead) -- **Session IDs are per-request** - Use for analytics and tracing, not as user identifiers -- **Thread IDs span requests** - Use for conversation grouping and context - - -### State Size Management - -Keep conversation history bounded to avoid memory issues: - -```typescript -import { createAgent } from '@agentuity/runtime'; - -const agent = createAgent({ - handler: async (ctx, input) => { - const maxMessages = 50; // Keep last 50 messages in memory - const archiveThreshold = 40; // Archive when reaching 40 messages - - // Get current messages from thread state - const messages = (c.thread.state.get('messages') as string[]) || []; - - // Add new message - messages.push(JSON.stringify(input)); - - // Archive old messages to KV when threshold is reached - if (messages.length >= archiveThreshold) { - const toArchive = messages.slice(0, messages.length - maxMessages); - - if (toArchive.length > 0) { - // Archive to KV storage - const archiveKey = `archive_${c.thread.id}_${Date.now()}`; - await c.kv.set('message-archives', archiveKey, { - threadId: c.thread.id, - messages: toArchive, - archivedAt: new Date().toISOString() - }, { - ttl: 604800 // Keep for 7 days - }); - - ctx.logger.info('Archived old messages', { - threadId: c.thread.id, - archivedCount: toArchive.length - }); - } - - // Keep only recent messages in state - const recentMessages = messages.slice(-maxMessages); - ctx.thread.state.set('messages', recentMessages); - } else { - ctx.thread.state.set('messages', messages); - } - - return { - processed: true, - messagesInMemory: messages.length - }; - } -}); - -export default agent; -``` - -**Key Points:** -- Set maximum size limits for state collections -- Archive old data to KV storage -- Keep recent data in state for fast access -- Prevent unbounded memory growth - -## Understanding Thread vs Session IDs - -Thread IDs and session IDs serve different purposes in the SDK: - -**Thread ID (`c.thread.id`):** -- Represents a conversation or interaction sequence -- Format: `thrd_<32-char-hex>` -- Stored in cookie `atid` for client persistence -- Same thread ID across multiple requests (up to 1 hour) -- Use for grouping related requests into conversations - -**Session ID (`c.sessionId`):** -- Represents a single request execution -- Format: `sess_<32-char-hex>` (or similar) -- Unique for each request -- New session ID even within the same thread -- Use for request tracing, logging, and analytics - -### Tracking with IDs - -Demonstrate the relationship between thread and session IDs: - -```typescript -import { createAgent } from '@agentuity/runtime'; - -const agent = createAgent({ - handler: async (ctx, input) => { - // Track unique sessions per thread - const sessionsSeen = (c.thread.state.get('sessionsSeen') as Set) || new Set(); - sessionsSeen.add(c.sessionId); - ctx.thread.state.set('sessionsSeen', sessionsSeen); - - // Log both IDs for correlation - ctx.logger.info('Request tracking', { - threadId: c.thread.id, // Same across conversation - sessionId: c.sessionId, // Unique per request - requestNumber: sessionsSeen.size, - threadDuration: Date.now() - (c.thread.state.get('threadStartTime') as number || Date.now()) - }); - - // Initialize thread tracking - if (!c.thread.state.has('threadStartTime')) { - ctx.thread.state.set('threadStartTime', Date.now()); - } - - return { - threadId: c.thread.id, - sessionId: c.sessionId, - requestsInThread: sessionsSeen.size, - explanation: { - threadId: 'Groups related requests into a conversation (up to 1 hour)', - sessionId: 'Uniquely identifies this specific request' - } - }; - } -}); - -export default agent; -``` - -**Expected Output:** -```javascript -// Request 1: -{ threadId: 'thrd_abc123...', sessionId: 'sess_xyz789...', requestsInThread: 1 } - -// Request 2 (same conversation): -{ threadId: 'thrd_abc123...', sessionId: 'sess_def456...', requestsInThread: 2 } -// Note: Same threadId, different sessionId - -// Request 3 (after 1 hour, new thread): -{ threadId: 'thrd_ghi789...', sessionId: 'sess_jkl012...', requestsInThread: 1 } -// Note: New threadId and sessionId -``` - -**Key Points:** -- Thread ID groups requests into conversations -- Session ID uniquely identifies each request -- Use thread ID for conversation context -- Use session ID for request tracing and analytics -- Both IDs are essential for different tracking purposes diff --git a/content/v1/archive/subagents-old.mdx b/content/v1/archive/subagents-old.mdx deleted file mode 100644 index b7d352a8..00000000 --- a/content/v1/archive/subagents-old.mdx +++ /dev/null @@ -1,747 +0,0 @@ ---- -title: Subagents -description: Organize related agents into parent-child hierarchies ---- - -Subagents organize related agents into parent-child hierarchies with one level of nesting. Use subagents to group related functionality under a common parent, share validation logic, and create hierarchical API structures. Each subagent maintains its own handler and routes while accessing the parent agent via `ctx.parent`. - -For routing patterns and middleware configuration, see the [Routing & Triggers Guide](/Guides/routing-triggers). For general agent patterns, see [Core Concepts](/Introduction/core-concepts). - -## Understanding Subagents - -Subagents are regular agents nested within a parent agent's directory. The file structure determines the relationship: agents in subdirectories become subagents automatically. This enables logical grouping of related operations while maintaining clear separation of concerns. - -**File Structure:** -``` -team/ # Parent agent -├── agent.ts # Parent logic -├── route.ts # Parent routes -├── members/ # Subagent: member operations -│ ├── agent.ts -│ └── route.ts -└── tasks/ # Subagent: task operations - ├── agent.ts - └── route.ts -``` - -**Resulting Structure:** -- Parent agent name: `team` -- Subagent names: `team.members`, `team.tasks` -- Parent routes: `/agent/team` -- Subagent routes: `/agent/team/members`, `/agent/team/tasks` - -### When to Use Subagents - -Use subagents when you have: - -- **Related operations in the same domain** - User profile, settings, and notifications belong together -- **Shared validation or authentication** - Parent validates permissions once for all subagents -- **Natural parent-child relationship** - Product catalog contains inventory, pricing, and reviews -- **Hierarchical API structure** - Routes reflect domain hierarchy (`/team/members`, `/team/tasks`) - -### When to Use Separate Agents - -Use separate agents when you have: - -- **Independent domains** - User management and payment processing are unrelated -- **Different ownership** - Multiple teams maintain different parts of the system -- **No shared context** - Agents don't need common validation or state -- **Flexible composition** - Agent may be called from many different contexts - -### Comparison Table - -| Feature | Subagents | Separate Agents | -|---------|-----------|-----------------| -| **File structure** | Nested directories | Flat structure | -| **Access pattern** | `c.agent.parent.child.run()` | `c.agent.agentName.run()` | -| **Routes** | Inherit parent path | Independent paths | -| **Shared logic** | Parent provides context | Shared via middleware | -| **Parent access** | `c.parent` available | N/A | -| **Coupling** | Moderate (shares parent) | Low (independent) | -| **Use case** | Related operations | Independent operations | - -## Creating Parent Agents - -Parent agents are regular agents with subdirectories containing subagents. The parent provides coordination logic, shared validation, and high-level operations. - -```typescript -// team/agent.ts -import { createAgent } from '@agentuity/runtime'; -import { z } from 'zod'; - -const agent = createAgent({ - schema: { - input: z.object({ - action: z.enum(['info', 'stats']) - }), - output: z.object({ - message: z.string(), - memberCount: z.number().optional(), - taskCount: z.number().optional() - }) - }, - handler: async (ctx, input) => { - if (input.action === 'info') { - return { message: 'Team management system' }; - } - - // Coordinate between subagents - const [members, tasks] = await Promise.all([ - ctx.agent.team.members.run({ action: 'count' }), - ctx.agent.team.tasks.run({ action: 'count' }) - ]); - - return { - message: 'Team statistics', - memberCount: members.count, - taskCount: tasks.count - }; - } -}); - -export default agent; - -// Routes: team/route.ts - standard routing applies -// GET /agent/team → calls agent with default input -// POST /agent/team → calls agent with request body -``` - -**Key Points:** -- Parent is a standard agent created with `createAgent()` -- Can call subagents via `ctx.agent.parent.child.run()` -- Provides high-level coordination between subagents -- Routes follow standard patterns (see [Routing & Triggers Guide](/Guides/routing-triggers)) - -## Creating Subagents - -Subagents are created by placing agent.ts and route.ts files in a subdirectory of the parent agent. The directory structure is automatically detected during bundling. - -**File Structure Requirements:** -- **Location**: Must be in a subdirectory of the parent agent -- **Files**: Both `agent.ts` and `route.ts` required -- **Depth**: One level only (no grandchildren) -- **Detection**: Automatically detected by the bundler - -**Naming Convention:** -- Agent name: `parent.child` (e.g., `team.members`) -- Property access: `ctx.agent.team.members` (nested object) -- Current agent name: `ctx.agentName` returns `"team.members"` - -### Basic Subagent - -Create a subagent for member management: - -```typescript -// team/members/agent.ts -import { createAgent } from '@agentuity/runtime'; -import { z } from 'zod'; - -const agent = createAgent({ - schema: { - input: z.object({ - action: z.enum(['list', 'add', 'remove', 'count']), - name: z.string().optional() - }), - output: z.object({ - members: z.array(z.string()), - count: z.number().optional() - }) - }, - handler: async (ctx, input) => { - const result = await c.kv.get('team-data', 'members'); - let members: string[] = result.exists && result.data ? result.data : []; - - if (input.action === 'add' && input.name) { - members.push(input.name); - await c.kv.set('team-data', 'members', members); - } else if (input.action === 'remove' && input.name) { - members = members.filter(m => m !== input.name); - await c.kv.set('team-data', 'members', members); - } else if (input.action === 'count') { - return { members, count: members.length }; - } - - return { members }; - } -}); - -export default agent; -``` - -**Key Points:** -- Same `createAgent()` syntax as regular agents -- Agent name automatically becomes `team.members` -- Access via `ctx.agentName` returns `"team.members"` -- Full schema validation and type inference - - -**Nesting Depth:** Only one level of nesting is supported. Grandchildren (`team/members/admins/agent.ts`) are not supported. If you need deeper organization, create separate subagents at the same level (e.g., `team/members/` and `team/memberAdmins/`). - - -### Subagent Routes - -Routes inherit the parent's path and follow standard patterns: - -```typescript -// team/members/route.ts -import { createRouter } from '@agentuity/runtime'; -import { zValidator } from '@hono/zod-validator'; -import { z } from 'zod'; - -const router = createRouter(); - -// GET /agent/team/members -router.get('/', async (c) => { - const result = await ctx.agent.team.members.run({ action: 'list' }); - return c.json(result); -}); - -// POST /agent/team/members/add -router.post('/add', zValidator('json', z.object({ name: z.string() })), async (c) => { - const data = c.req.valid('json'); - const result = await ctx.agent.team.members.run({ action: 'add', name: data.name }); - return c.json(result); -}); - -export default router; -``` - -**Key Points:** -- Routes inherit parent path: `/agent/team/members` -- Access subagent via `ctx.agent.team.members.run()` -- Standard Hono routing patterns apply -- See [Routing & Triggers Guide](/Guides/routing-triggers) for complete patterns - -## Accessing Subagents - -Subagents are accessed via nested properties on `ctx.agent` or `ctx.agent`. Type inference provides autocomplete and compile-time validation when schemas are defined. - -```typescript -// From routes or other agents -const router = createRouter(); - -router.get('/summary', async (c) => { - // Parallel execution - const [members, tasks] = await Promise.all([ - ctx.agent.team.members.run({ action: 'list' }), - ctx.agent.team.tasks.run({ action: 'list' }) - ]); - - return c.json({ - memberCount: members.members.length, - taskCount: tasks.tasks.length - }); -}); - -// From another agent -const agent = createAgent({ - handler: async (ctx, input) => { - const members = await c.agent.team.members.run({ action: 'count' }); - return { count: members.count }; - } -}); -``` - -**Key Points:** -- Nested property access: `c.agent.parent.child.run()` -- Works from routes and other agents -- Supports parallel execution with `Promise.all()` -- Type-safe when both agents have schemas - -**Accessing Sibling Subagents:** - -From within a subagent, access siblings via the full path (not via `ctx.sibling`): - -```typescript -// From team/members/agent.ts -const tasksData = await c.agent.team.tasks.run({ action: 'list' }); // ✓ -``` - - -**Type Safety:** When agents define schemas with Zod/Valibot/ArkType, TypeScript provides full type inference for inputs and outputs, including autocomplete and compile-time validation. - - -## Parent Context Access - -Subagents access their parent agent via `c.parent`. This enables validation, shared configuration, and coordination patterns while maintaining separation of concerns. - -### Basic Usage - -The `c.parent` property is available only in subagent handlers: - -```typescript -// team/members/agent.ts -const agent = createAgent({ - handler: async (ctx, input) => { - let parentInfo: string | undefined; - - // Check if parent is available - if (c.parent) { - const parentData = await c.parent.run({ action: 'info' }); - parentInfo = parentData.message; - - ctx.logger.info('Retrieved parent context', { - parentMessage: parentInfo, - currentAgent: c.agentName // "team.members" - }); - } - - return { result: 'Processed', parentInfo }; - } -}); -``` - -**Key Points:** -- `c.parent` available only in subagents (undefined in parent) -- Call parent via `c.parent.run(input)` -- Parent execution is synchronous (blocks subagent) -- Always check `if (c.parent)` for type safety - -### Shared Validation Pattern - -Use parent for centralized validation logic: - -```typescript -// team/agent.ts - Parent validates permissions -const teamAgent = createAgent({ - schema: { - input: z.object({ - userId: z.string(), - teamId: z.string(), - requiredRole: z.enum(['member', 'admin', 'owner']).optional() - }), - output: z.object({ - authorized: z.boolean(), - userRole: z.string() - }) - }, - handler: async (ctx, input) => { - const result = await c.kv.get('team-memberships', `${input.teamId}:${input.userId}`); - - if (!result.exists) { - throw new Error('User is not a member of this team'); - } - - const membership = result.data as { role: string }; - - // Check role if required - if (input.requiredRole) { - const roleHierarchy = { member: 1, admin: 2, owner: 3 }; - const userLevel = roleHierarchy[membership.role as keyof typeof roleHierarchy] || 0; - const requiredLevel = roleHierarchy[input.requiredRole]; - - if (userLevel < requiredLevel) { - throw new Error(`Requires ${input.requiredRole} role`); - } - } - - return { authorized: true, userRole: membership.role }; - } -}); - -// team/members/agent.ts - Uses parent validation -const membersAgent = createAgent({ - handler: async (ctx, input) => { - // Validate with admin role requirement - if (c.parent) { - await c.parent.run({ - userId: input.userId, - teamId: input.teamId, - requiredRole: 'admin' - }); - } - - // Admin-only operation - const members = await c.kv.get('team-members', input.teamId); - return { members: members.data || [] }; - } -}); -``` - -**Key Points:** -- Parent provides centralized validation -- Subagents call parent before executing -- Validation errors propagate to caller -- Reduces duplication across subagents - -### When to Use Parent Access - -Use `c.parent` for: -- **Validation** - Check permissions before executing subagent logic -- **Shared configuration** - Retrieve settings managed by parent -- **Logging context** - Include parent information in logs - -Avoid `c.parent` for: -- **Core business logic** - Subagent should be mostly independent -- **Data operations** - Use storage directly, not via parent -- **Frequent calls** - Each call is synchronous and blocks execution -- **Circular calls** - Avoid parent calling child that calls parent back (creates infinite loop) - -## Route Organization - -Routes in subagent hierarchies follow the parent path structure. Middleware applied to parent routes cascades to all subagent routes. - -### Path Inheritance - -Subagent routes automatically inherit the parent path: - -``` -Parent: GET /agent/team - POST /agent/team - -Subagent: GET /agent/team/members - POST /agent/team/members/add - POST /agent/team/members/remove -``` - -### Middleware Cascade - -Middleware applied to parent routes cascades to all subagents: - -```typescript -// team/route.ts - Parent with authentication -const router = createRouter(); - -router.use('/*', async (c, next) => { - const token = c.req.header('Authorization'); - if (!token) return c.json({ error: 'Unauthorized' }, 401); - - c.set('userId', await getUserIdFromToken(token)); - await next(); -}); - -// All subagent routes inherit this auth middleware -``` - -**Key Points:** -- Parent middleware automatically applies to all subagent routes -- Subagents can add additional middleware -- Middleware executes in order: parent first, then subagent - - -For complete routing documentation including HTTP methods, middleware patterns, and specialized routes (WebSocket, SSE, cron), see the [Routing & Triggers Guide](/Guides/routing-triggers). - - -## Common Patterns - -### User Account Management - -Organize user-related operations under a parent user agent: - -``` -user/ -├── agent.ts # Core user operations -├── route.ts -├── profile/ # Profile management -│ ├── agent.ts -│ └── route.ts -├── settings/ # User settings -│ ├── agent.ts -│ └── route.ts -└── notifications/ # Notification preferences - ├── agent.ts - └── route.ts -``` - -**Parent agent handles core user logic:** - -```typescript -// user/agent.ts -const userAgent = createAgent({ - schema: { - input: z.object({ - userId: z.string(), - action: z.enum(['get', 'validate']) - }), - output: z.object({ - user: z.object({ - id: z.string(), - email: z.string(), - status: z.enum(['active', 'suspended']) - }).optional(), - valid: z.boolean().optional() - }) - }, - handler: async (ctx, input) => { - const result = await c.kv.get('users', input.userId); - if (!result.exists) throw new Error('User not found'); - - const user = result.data; - if (input.action === 'validate') { - return { valid: user.status === 'active' }; - } - - return { user }; - } -}); -``` - -**Subagent manages profile data:** - -```typescript -// user/profile/agent.ts -const profileAgent = createAgent({ - schema: { - input: z.object({ - userId: z.string(), - action: z.enum(['get', 'update']), - profile: z.object({ - displayName: z.string().optional(), - bio: z.string().optional(), - avatar: z.string().url().optional() - }).optional() - }), - output: z.object({ - profile: z.object({ - displayName: z.string(), - bio: z.string(), - avatar: z.string().url().optional() - }) - }) - }, - handler: async (ctx, input) => { - // Validate user via parent - if (c.parent) { - const validation = await c.parent.run({ - userId: input.userId, - action: 'validate' - }); - if (!validation.valid) { - throw new Error('User account is not active'); - } - } - - const result = await c.kv.get(`profile-${input.userId}`, 'user-profiles'); - let profile = result.exists ? result.data : { - displayName: 'New User', - bio: '' - }; - - if (input.action === 'update' && input.profile) { - profile = { ...profile, ...input.profile }; - await c.kv.set('user-profiles', `profile-${input.userId}`, profile); - } - - return { profile }; - } -}); -``` - -**Key Points:** -- Parent validates user status -- Subagents manage specific user data -- Clear separation: core vs. profile vs. settings vs. notifications -- Each subagent can be tested independently - -### Other Domain Patterns - -**E-commerce Product Management:** -``` -product/ -├── inventory/ # Stock management -├── pricing/ # Price operations -└── reviews/ # Customer reviews -``` -- Parent: Product validation and catalog management -- Subagents: Domain-specific operations (inventory, pricing, reviews) -- Use case: E-commerce platforms requiring organized product operations - -**Content Management:** -``` -content/ -├── drafts/ # Draft management -├── publishing/ # Publish workflow -└── media/ # Media attachments -``` -- Parent: Content workflow coordination -- Subagents: Stage-specific operations (drafting, publishing, media) -- Use case: CMS platforms with multi-step content workflows - -For complete working examples of these patterns, see the [Examples](/Examples) page. - -## Best Practices - -### Design Guidelines - -**Parent Coordinates, Children Execute:** -```typescript -// Good: Parent coordinates -const teamAgent = createAgent({ - handler: async (ctx, input) => { - const [members, tasks] = await Promise.all([ - ctx.agent.team.members.run({ action: 'count' }), - ctx.agent.team.tasks.run({ action: 'count' }) - ]); - return { memberCount: members.count, taskCount: tasks.count }; - } -}); - -// Bad: Parent implements domain logic -const teamAgent = createAgent({ - handler: async (ctx, input) => { - const members = await c.kv.get('members', 'list'); - return { members: members.data.length }; // Should delegate to subagent - } -}); -``` - -**Keep Subagents Independent:** -- Minimize dependencies on parent -- Use parent for validation, not core logic -- Each subagent should have clear responsibility - -**Limit Depth to One Level:** -```typescript -// Supported -team/members/agent.ts - -// Not supported -team/members/admins/agent.ts // Too deep - -// Alternative -team/members/agent.ts -team/memberAdmins/agent.ts // Separate subagent -``` - -### Performance - -**Cache parent context if calling multiple times:** -```typescript -const membersAgent = createAgent({ - handler: async (ctx, input) => { - const parentData = c.parent ? await c.parent.run({ teamId: input.teamId }) : null; - // Use parentData multiple times without re-calling - return { processed: true }; - } -}); -``` - -**Execute independent subagent calls in parallel:** -```typescript -// Good -const [members, tasks] = await Promise.all([ - c.agent.team.members.run({ action: 'count' }), - c.agent.team.tasks.run({ action: 'count' }) -]); - -// Bad - slower when independent -const members = await c.agent.team.members.run({ action: 'count' }); -const tasks = await c.agent.team.tasks.run({ action: 'count' }); -``` - -### Testing - -**Test parent and children independently:** - -```typescript -// Test parent with mocked subagents -describe('Team Agent', () => { - it('should coordinate subagents', async () => { - const mockCtx = { - agent: { - team: { - members: { run: jest.fn().mockResolvedValue({ count: 5 }) }, - tasks: { run: jest.fn().mockResolvedValue({ count: 10 }) } - } - } - }; - const result = await teamAgent.handler(mockCtx, { action: 'stats' }); - expect(result.memberCount).toBe(5); - }); -}); - -// Test subagent with mocked parent -describe('Members Subagent', () => { - it('should validate via parent', async () => { - const mockCtx = { - parent: { run: jest.fn().mockResolvedValue({ authorized: true }) }, - kv: { get: jest.fn(), set: jest.fn() } - }; - await membersAgent.handler(mockCtx, { action: 'add', name: 'Alice' }); - expect(mockCtx.parent.run).toHaveBeenCalled(); - }); -}); -``` - -### Error Handling - -**Let validation errors propagate:** -```typescript -const membersAgent = createAgent({ - handler: async (ctx, input) => { - if (c.parent) { - await c.parent.run({ userId: input.userId, teamId: input.teamId }); - // Parent throws on validation failure - error propagates to caller - } - return { members: [] }; - } -}); -``` - -**Graceful degradation for optional parent calls:** -```typescript -const membersAgent = createAgent({ - handler: async (ctx, input) => { - if (c.parent) { - try { - await c.parent.run({ userId: input.userId }); - } catch (error) { - ctx.logger.warn('Parent validation unavailable', { error }); - // Continue with degraded functionality - } - } - return { members: [] }; - } -}); -``` - -## React Integration - -The SDK generates type-safe React hooks for accessing subagents: - -```typescript -// Generated types -export type AgentClient = { - team: { - run: (input: TeamInput) => Promise; - members: { - run: (input: MembersInput) => Promise; - }; - }; -}; - -// Usage in React components -import { useAgent } from '@/lib/agentuity/client/react'; - -export function TeamDashboard() { - const loadData = async () => { - // Nested property access with full type safety - const teamInfo = await useAgent.team.run({ action: 'stats' }); - const members = await useAgent.team.members.run({ action: 'list' }); - - // TypeScript knows the exact types - console.log(members.members.length); - }; -} -``` - -**Key Points:** -- Nested property access: `useAgent.parent.child.run()` -- Full TypeScript inference -- Same patterns as backend usage - -## Next Steps - -**Explore Related Guides:** -- [Routing](/Guides/routing): Complete routing patterns and middleware -- [Schema Validation](/Guides/schema-validation): Type-safe schemas with Zod/Valibot/ArkType -- [Events](/Guides/events): Monitor agent execution with lifecycle hooks -- [Sessions & Threads](/Guides/sessions-threads): Stateful context management - -**API Reference:** -- [AgentContext](/api-reference#agentcontext): Complete context API including `c.parent` -- [createAgent](/api-reference#createagent): Agent creation and configuration -- [createRouter](/api-reference#createrouter): Route definition and middleware - -**Examples:** -- [Examples](/Examples): Additional patterns and complete working examples diff --git a/content/v1/meta.json b/content/v1/meta.json index fc19e28f..e1de2aaf 100644 --- a/content/v1/meta.json +++ b/content/v1/meta.json @@ -1,5 +1,5 @@ { - "title": "Latest (v1)", + "title": "Preview", "description": "Agentuity SDK 1.x", "root": true, "pages": [ diff --git a/content/v1/migration-guide.mdx b/content/v1/migration-guide.mdx index 67f21772..636fddee 100644 --- a/content/v1/migration-guide.mdx +++ b/content/v1/migration-guide.mdx @@ -524,7 +524,7 @@ router.email('support@example.com', async (email, ctx) => { const htmlBody = email.html(); const attachments = email.attachments(); - // TODO: Email reply functionality is not yet available in v1 + // Note: Email reply functionality is coming soon return { status: 'processed' }; }); @@ -743,8 +743,6 @@ const agent = createAgent({ Organize complex agents with parent-child relationships. -TODO: Add example of subagents - **Project Structure:** ``` src/agents/ From 3a4969119194007d07209d305ce616c231d70a3d Mon Sep 17 00:00:00 2001 From: Parteek Singh Date: Mon, 1 Dec 2025 16:33:23 -0800 Subject: [PATCH 16/63] Add remaining "Build" docs --- content/v1/Build/APIs/calling-agents.mdx | 259 +++++ content/v1/Build/APIs/creating-api-routes.mdx | 280 +++++ content/v1/Build/APIs/meta.json | 9 + content/v1/Build/APIs/middleware-auth.mdx | 327 ++++++ content/v1/Build/APIs/when-to-use-apis.mdx | 201 ++++ content/v1/Build/Frontend/advanced-hooks.mdx | 141 +++ .../Build/Frontend/deployment-scenarios.mdx | 120 +++ content/v1/Build/Frontend/meta.json | 4 + content/v1/Build/Frontend/provider-setup.mdx | 86 ++ content/v1/Build/Frontend/react-hooks.mdx | 186 ++++ content/v1/Build/Observability/logging.mdx | 162 +++ content/v1/Build/Observability/meta.json | 4 + .../Observability/sessions-debugging.mdx | 150 +++ content/v1/Build/Observability/tracing.mdx | 211 ++++ content/v1/Build/Storage/custom.mdx | 119 +++ content/v1/Build/Storage/durable-streams.mdx | 284 ++++++ content/v1/Build/Storage/key-value.mdx | 208 ++++ content/v1/Build/Storage/meta.json | 4 + content/v1/Build/Storage/object.mdx | 189 ++++ content/v1/Build/Storage/vector.mdx | 256 +++++ content/v1/Build/meta.json | 2 +- content/v1/Guides/key-value-storage.mdx | 277 ----- content/v1/Guides/object-storage.mdx | 529 ---------- content/v1/Guides/vector-storage.mdx | 954 ------------------ 24 files changed, 3201 insertions(+), 1761 deletions(-) create mode 100644 content/v1/Build/APIs/calling-agents.mdx create mode 100644 content/v1/Build/APIs/creating-api-routes.mdx create mode 100644 content/v1/Build/APIs/meta.json create mode 100644 content/v1/Build/APIs/middleware-auth.mdx create mode 100644 content/v1/Build/APIs/when-to-use-apis.mdx create mode 100644 content/v1/Build/Frontend/advanced-hooks.mdx create mode 100644 content/v1/Build/Frontend/deployment-scenarios.mdx create mode 100644 content/v1/Build/Frontend/meta.json create mode 100644 content/v1/Build/Frontend/provider-setup.mdx create mode 100644 content/v1/Build/Frontend/react-hooks.mdx create mode 100644 content/v1/Build/Observability/logging.mdx create mode 100644 content/v1/Build/Observability/meta.json create mode 100644 content/v1/Build/Observability/sessions-debugging.mdx create mode 100644 content/v1/Build/Observability/tracing.mdx create mode 100644 content/v1/Build/Storage/custom.mdx create mode 100644 content/v1/Build/Storage/durable-streams.mdx create mode 100644 content/v1/Build/Storage/key-value.mdx create mode 100644 content/v1/Build/Storage/meta.json create mode 100644 content/v1/Build/Storage/object.mdx create mode 100644 content/v1/Build/Storage/vector.mdx delete mode 100644 content/v1/Guides/key-value-storage.mdx delete mode 100644 content/v1/Guides/object-storage.mdx delete mode 100644 content/v1/Guides/vector-storage.mdx diff --git a/content/v1/Build/APIs/calling-agents.mdx b/content/v1/Build/APIs/calling-agents.mdx new file mode 100644 index 00000000..03b806be --- /dev/null +++ b/content/v1/Build/APIs/calling-agents.mdx @@ -0,0 +1,259 @@ +--- +title: Calling Agents from APIs +description: Invoke agents from API routes using c.agent +--- + +API routes can call any agent in your project using `c.agent`. Use APIs as lightweight entry points that hand off work to agents, keeping schema validation and evaluations where they belong. + +## Basic Agent Call + +Call an agent by name through the `c.agent` registry: + +```typescript +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +router.post('/chat', async (c) => { + const { message } = await c.req.json(); + + // Call the chat agent + const result = await c.agent.chat.run({ message }); + + return c.json(result); +}); + +export default router; +``` + +The agent receives validated input (if it has a schema) and returns typed output. + +## Agent Registry + +The `c.agent` object contains all agents in your project, accessed by their directory name: + +``` +src/agents/ +├── chat/ → c.agent.chat +├── summarizer/ → c.agent.summarizer +└── team/ + ├── members/ → c.agent.team.members + └── tasks/ → c.agent.team.tasks +``` + +```typescript +router.post('/process', async (c) => { + const input = await c.req.json(); + + // Call different agents + const chatResult = await c.agent.chat.run({ message: input.text }); + const summary = await c.agent.summarizer.run({ content: input.text }); + + // Call subagents + const members = await c.agent.team.members.run({ teamId: input.teamId }); + + return c.json({ chatResult, summary, members }); +}); +``` + +## Parallel Agent Calls + +Run multiple agents concurrently when they don't depend on each other: + +```typescript +router.post('/analyze', async (c) => { + const { content } = await c.req.json(); + + // Run agents in parallel + const [sentiment, topics, summary] = await Promise.all([ + c.agent.sentimentAnalyzer.run({ text: content }), + c.agent.topicExtractor.run({ text: content }), + c.agent.summarizer.run({ content }), + ]); + + return c.json({ sentiment, topics, summary }); +}); +``` + +## Background Agent Calls + +Use `c.waitUntil()` to run agents after responding to the client: + +```typescript +router.post('/webhook', async (c) => { + const payload = await c.req.json(); + + // Acknowledge immediately + c.waitUntil(async () => { + // Process in background + await c.agent.webhookProcessor.run(payload); + c.var.logger.info('Webhook processed'); + }); + + return c.json({ received: true }); +}); +``` + + +Webhook providers expect fast responses (usually under 3 seconds). Use `c.waitUntil()` to acknowledge receipt immediately and process the payload in the background. + + +## Error Handling + +Wrap agent calls in try-catch for graceful error handling: + +```typescript +router.post('/safe-chat', async (c) => { + const { message } = await c.req.json(); + + try { + const result = await c.agent.chat.run({ message }); + return c.json({ success: true, result }); + } catch (error) { + c.var.logger.error('Agent call failed', { + agent: 'chat', + error: error instanceof Error ? error.message : String(error), + }); + + return c.json( + { success: false, error: 'Chat processing failed' }, + 500 + ); + } +}); +``` + +## Full Example: Multi-Endpoint API + +Combine authentication, validation, and multiple agent calls in a single API file: + +```typescript +import { createRouter } from '@agentuity/runtime'; +import { createMiddleware } from 'hono/factory'; +import { zValidator } from '@hono/zod-validator'; +import { z } from 'zod'; + +const router = createRouter(); + +// Auth middleware +const authMiddleware = createMiddleware(async (c, next) => { + const apiKey = c.req.header('X-API-Key'); + if (!apiKey) { + return c.json({ error: 'API key required' }, 401); + } + + const keyData = await c.kv.get('api-keys', apiKey); + if (!keyData.exists) { + return c.json({ error: 'Invalid API key' }, 401); + } + + c.set('userId', keyData.data.userId); + await next(); +}); + +// Apply auth to all routes +router.use('/*', authMiddleware); + +// Chat endpoint +const chatSchema = z.object({ + message: z.string().min(1), + conversationId: z.string().optional(), +}); + +router.post('/chat', + zValidator('json', chatSchema), + async (c) => { + const userId = c.var.userId; + const { message, conversationId } = c.req.valid('json'); + + const result = await c.agent.chat.run({ + message, + userId, + conversationId, + }); + + // Track usage in background + c.waitUntil(async () => { + await c.kv.set('usage', `${userId}:${Date.now()}`, { + endpoint: 'chat', + tokens: result.tokensUsed, + }); + }); + + return c.json(result); + } +); + +// Summarization endpoint +const summarizeSchema = z.object({ + content: z.string().min(10), + maxLength: z.number().optional().default(200), +}); + +router.post('/summarize', + zValidator('json', summarizeSchema), + async (c) => { + const { content, maxLength } = c.req.valid('json'); + + const result = await c.agent.summarizer.run({ content, maxLength }); + + return c.json(result); + } +); + +// Multi-agent analysis endpoint +const analyzeSchema = z.object({ + content: z.string().min(1), +}); + +router.post('/analyze', + zValidator('json', analyzeSchema), + async (c) => { + const { content } = c.req.valid('json'); + + // Run multiple agents in parallel + const [sentiment, entities, summary] = await Promise.all([ + c.agent.sentimentAnalyzer.run({ text: content }), + c.agent.entityExtractor.run({ text: content }), + c.agent.summarizer.run({ content, maxLength: 100 }), + ]); + + return c.json({ + sentiment: sentiment.score, + entities: entities.items, + summary: summary.text, + }); + } +); + +export default router; +``` + +## Type Safety + +If your agents have schemas, TypeScript provides full type checking: + +```typescript +// Agent with schema (src/agents/chat/agent.ts) +const chatAgent = createAgent({ + schema: { + input: z.object({ message: z.string() }), + output: z.object({ response: z.string(), tokensUsed: z.number() }), + }, + handler: async (ctx, input) => { ... }, +}); + +// In API route - TypeScript knows the types +router.post('/chat', async (c) => { + const result = await c.agent.chat.run({ message: 'Hello' }); + // result is typed as { response: string, tokensUsed: number } + + return c.json({ text: result.response }); +}); +``` + +## Next Steps + +- [Creating Agents](/Build/Agents/creating-agents): Build agents with schemas and handlers +- [Calling Other Agents](/Build/Agents/calling-other-agents): Agent-to-agent communication patterns +- [Creating API Routes](/Build/APIs/creating-api-routes): Complete API routing guide diff --git a/content/v1/Build/APIs/creating-api-routes.mdx b/content/v1/Build/APIs/creating-api-routes.mdx new file mode 100644 index 00000000..f9a67f73 --- /dev/null +++ b/content/v1/Build/APIs/creating-api-routes.mdx @@ -0,0 +1,280 @@ +--- +title: Creating API Routes +description: Build lightweight HTTP endpoints with createRouter() in src/apis/ +--- + +API routes provide a lightweight way to expose HTTP endpoints without the overhead of agent schemas and validation. Use them for health checks, webhooks, static data, or any endpoint that doesn't need structured agent processing. + + +API routes let your application respond to HTTP requests (GET, POST, etc.). If you're new to building APIs, check out [MDN's HTTP overview](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Overview) or [Hono's documentation](https://hono.dev) for the router we use under the hood. + + +## Basic API Route + +Create a `route.ts` file in the `src/apis/` directory: + +```typescript +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +router.get('/', (c) => { + return c.json({ status: 'healthy', timestamp: Date.now() }); +}); + +router.get('/version', (c) => { + return c.json({ version: '1.0.0' }); +}); + +export default router; +``` + + +Routes in `src/apis/` are automatically prefixed with `/api/`. A route at `src/apis/health/route.ts` becomes `/api/health/`. + + +To test locally, run `bun dev` and use curl, Postman, or your browser to hit `http://localhost:3500/api/...`. + +## Directory Structure + +``` +src/ +├── agents/ # Agent routes (prefixed with /agent/) +│ └── chat/ +│ ├── agent.ts # Required for agents +│ └── route.ts +└── apis/ # API routes (prefixed with /api/) + ├── health/ + │ └── route.ts # Only route.ts needed + ├── webhooks/ + │ └── route.ts + └── route.ts # Root API at /api/ +``` + +API directories only need a `route.ts` file. No `agent.ts` required. + +## HTTP Methods + +The router supports all standard HTTP methods: + +```typescript +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +router.get('/items', async (c) => { + const items = await fetchItems(); + return c.json(items); +}); + +router.post('/items', async (c) => { + const body = await c.req.json(); + const created = await createItem(body); + return c.json(created, 201); +}); + +router.put('/items/:id', async (c) => { + const id = c.req.param('id'); + const body = await c.req.json(); + const updated = await updateItem(id, body); + return c.json(updated); +}); + +router.delete('/items/:id', async (c) => { + const id = c.req.param('id'); + await deleteItem(id); + return c.json({ deleted: true }); +}); + +export default router; +``` + +## Route Parameters + +Capture URL segments with `:paramName`: + +```typescript +router.get('/users/:userId/posts/:postId', async (c) => { + const userId = c.req.param('userId'); + const postId = c.req.param('postId'); + + return c.json({ userId, postId }); +}); + +// Wildcard parameters for file paths +router.get('/files/:path{.*}', async (c) => { + const filePath = c.req.param('path'); + // filePath captures everything after /files/ + return c.text(`File: ${filePath}`); +}); +``` + +## Request and Response + +**Reading request data:** + +```typescript +router.post('/data', async (c) => { + // Body parsing + const jsonBody = await c.req.json(); + const textBody = await c.req.text(); + const formData = await c.req.formData(); + const arrayBuffer = await c.req.arrayBuffer(); + + // Headers and query params + const authHeader = c.req.header('Authorization'); + const page = c.req.query('page') || '1'; + + return c.json({ received: true }); +}); +``` + +**Sending responses:** + +```typescript +router.get('/examples', (c) => { +// JSON + return c.json({ data: 'value' }); + + // With status code + return c.json({ error: 'Not found' }, 404); + + // Plain text + return c.text('OK'); + + // Binary data + return c.body(new Uint8Array([1, 2, 3]), 200, { + 'Content-Type': 'application/octet-stream', + }); + + // Redirect + return c.redirect('/other-path'); +}); +``` + + +Pick a standard error format and use it everywhere. For example: `{ error: string, code?: string }`. This makes client-side error handling predictable. + + +## Accessing Services + +API routes have access to all Agentuity services through the route context: + +```typescript +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +router.post('/cache', async (c) => { + const { key, value } = await c.req.json(); + + // Key-value storage + await c.kv.set('api-cache', key, value, { ttl: 3600 }); + + return c.json({ cached: true }); +}); + +router.get('/cache/:key', async (c) => { + const key = c.req.param('key'); + const result = await c.kv.get('api-cache', key); + + if (!result.exists) { + return c.json({ error: 'Not found' }, 404); + } + + return c.json({ value: result.data }); +}); + +router.post('/upload/:bucket/:filename', async (c) => { + const bucket = c.req.param('bucket'); + const filename = c.req.param('filename'); + const data = new Uint8Array(await c.req.arrayBuffer()); + const contentType = c.req.header('Content-Type') || 'application/octet-stream'; + + // Object storage + await c.objectstore.put(bucket, filename, data, { contentType }); + + return c.json({ uploaded: true, bucket, filename }); +}); + +// Vector search +router.post('/search', async (c) => { + const { query } = await c.req.json(); + + const results = await c.vector.search('documents', { + query, + limit: 5, + }); + + return c.json({ results }); +}); + +export default router; +``` + +**Available services:** + +| Service | Description | +|---------|-------------| +| `c.kv` | Key-value storage | +| `c.vector` | Vector database | +| `c.objectstore` | Object/file storage | +| `c.stream` | Durable stream storage | +| `c.var.logger` | Structured logging | + +## Logging + + +In route handlers, use `c.var.logger`. In agent handlers, use `ctx.logger` directly. + + +Use structured logging instead of `console.log`: + +```typescript +router.post('/webhook', async (c) => { + const payload = await c.req.json(); + + c.var.logger.info('Webhook received', { + source: payload.source, + eventType: payload.type, + }); + + try { + await processWebhook(payload); + c.var.logger.info('Webhook processed successfully'); + return c.json({ processed: true }); + } catch (error) { + c.var.logger.error('Webhook processing failed', { error }); + return c.json({ error: 'Processing failed' }, 500); + } +}); +``` + +## Background Tasks + +Use `c.waitUntil()` for work that should complete after the response is sent: + +```typescript +router.post('/events', async (c) => { + const event = await c.req.json(); + + // Respond immediately + c.waitUntil(async () => { + // This runs after the response is sent + await c.kv.set('events', `event-${Date.now()}`, event); + c.var.logger.info('Event stored in background'); + }); + + return c.json({ accepted: true }); +}); +``` + + +If your API is called from a browser on a different domain, you'll need to configure CORS. See [App Configuration](/Get-Started/app-configuration) for details on setting up `createApp({ cors: ... })`. + + +## Next Steps + +- [When to Use APIs](/Build/APIs/when-to-use-apis): Decision guide for APIs vs agents +- [Adding Middleware](/Build/APIs/middleware-auth): Authentication and request processing +- [Calling Agents from APIs](/Build/APIs/calling-agents): Invoke agents from API routes diff --git a/content/v1/Build/APIs/meta.json b/content/v1/Build/APIs/meta.json new file mode 100644 index 00000000..b852706b --- /dev/null +++ b/content/v1/Build/APIs/meta.json @@ -0,0 +1,9 @@ +{ + "title": "APIs", + "pages": [ + "creating-api-routes", + "when-to-use-apis", + "middleware-auth", + "calling-agents" + ] +} diff --git a/content/v1/Build/APIs/middleware-auth.mdx b/content/v1/Build/APIs/middleware-auth.mdx new file mode 100644 index 00000000..120f9e9e --- /dev/null +++ b/content/v1/Build/APIs/middleware-auth.mdx @@ -0,0 +1,327 @@ +--- +title: Middleware and Authentication +description: Add authentication, validation, and request processing to API routes +--- + +Agentuity routes are built on [Hono](https://hono.dev), giving you access to Hono's middleware system. Use middleware to add authentication, logging, CORS, rate limiting, and other shared request processing. + +## Basic Middleware + +Create middleware with `createMiddleware` from Hono: + +```typescript +import { createRouter } from '@agentuity/runtime'; +import { createMiddleware } from 'hono/factory'; + +const router = createRouter(); + +// Middleware that logs all requests +const loggerMiddleware = createMiddleware(async (c, next) => { + const start = Date.now(); + await next(); + const duration = Date.now() - start; + c.var.logger.info('Request completed', { + method: c.req.method, + path: c.req.path, + duration, + }); +}); + +// Apply to specific route +router.get('/data', loggerMiddleware, (c) => { + return c.json({ data: 'value' }); +}); + +export default router; +``` + +## Authentication Patterns + +### API Key Authentication + +```typescript +import { createRouter } from '@agentuity/runtime'; +import { createMiddleware } from 'hono/factory'; + +const router = createRouter(); + +const apiKeyAuth = createMiddleware(async (c, next) => { + const apiKey = c.req.header('X-API-Key'); + + if (!apiKey) { + return c.json({ error: 'API key required' }, 401); + } + + // Validate against stored keys + const keyData = await c.kv.get('api-keys', apiKey); + if (!keyData.exists) { + return c.json({ error: 'Invalid API key' }, 401); + } + + // Add user info to context for downstream handlers + c.set('apiKeyOwner', keyData.data.ownerId); + await next(); +}); + +router.get('/protected', apiKeyAuth, (c) => { + const ownerId = c.var.apiKeyOwner; + return c.json({ message: 'Access granted', ownerId }); +}); + +export default router; +``` + +### Bearer Token (JWT) + +```typescript +import { createRouter } from '@agentuity/runtime'; +import { createMiddleware } from 'hono/factory'; +import { verify } from 'hono/jwt'; + +const router = createRouter(); + +const jwtAuth = createMiddleware(async (c, next) => { + const authHeader = c.req.header('Authorization'); + + if (!authHeader?.startsWith('Bearer ')) { + return c.json({ error: 'Bearer token required' }, 401); + } + + const token = authHeader.slice(7); + + try { + const payload = await verify(token, process.env.JWT_SECRET!); + c.set('user', payload); + await next(); + } catch { + return c.json({ error: 'Invalid token' }, 401); + } +}); + +router.get('/me', jwtAuth, (c) => { + const user = c.var.user; + return c.json({ userId: user.sub, email: user.email }); +}); + +export default router; +``` + +### Third-Party Auth Providers + +Services like [Clerk](https://clerk.dev), [Auth0](https://auth0.com), and [Supabase Auth](https://supabase.com/auth) provide drop-in authentication. Here's an example with Clerk: + +```typescript +import { createRouter } from '@agentuity/runtime'; +import { clerkMiddleware, getAuth } from '@hono/clerk-auth'; + +const router = createRouter(); + +// Apply Clerk middleware +router.use('/*', clerkMiddleware()); + +router.get('/public', (c) => { + return c.json({ message: 'Public endpoint' }); +}); + +router.get('/protected', (c) => { + const auth = getAuth(c); + + if (!auth?.userId) { + return c.json({ error: 'Unauthorized' }, 401); + } + + return c.json({ userId: auth.userId }); +}); + +export default router; +``` + + +For other auth providers, check if they have Hono middleware available, or use JWT validation with their tokens. + + +## Request Validation + +Use `zValidator` for type-safe request validation: + +```typescript +import { createRouter } from '@agentuity/runtime'; +import { zValidator } from '@hono/zod-validator'; +import { z } from 'zod'; + +const router = createRouter(); + +const createItemSchema = z.object({ + name: z.string().min(1).max(100), + price: z.number().positive(), + category: z.enum(['electronics', 'clothing', 'food']), +}); + +router.post('/items', + zValidator('json', createItemSchema), + async (c) => { + // data is typed as { name: string, price: number, category: string } + const data = c.req.valid('json'); + + await c.kv.set('items', crypto.randomUUID(), data); + + return c.json({ created: true, item: data }); + } +); + +export default router; +``` + +### Validating Multiple Sources + +```typescript +const querySchema = z.object({ + page: z.coerce.number().default(1), + limit: z.coerce.number().default(10), +}); + +const headerSchema = z.object({ + 'x-api-version': z.string().optional(), +}); + +router.get('/items', + zValidator('query', querySchema), + zValidator('header', headerSchema), + async (c) => { + const { page, limit } = c.req.valid('query'); + const headers = c.req.valid('header'); + + return c.json({ page, limit, apiVersion: headers['x-api-version'] }); + } +); +``` + +## Route-Level Middleware + +Apply middleware to all routes in a router with `router.use()`: + +```typescript +import { createRouter } from '@agentuity/runtime'; +import { createMiddleware } from 'hono/factory'; + +const router = createRouter(); + +// Apply to all routes in this file +router.use('/*', createMiddleware(async (c, next) => { + c.var.logger.info('API request', { path: c.req.path }); + await next(); +})); + +// Apply to specific path prefix +router.use('/admin/*', adminAuthMiddleware); + +router.get('/public', (c) => c.text('Anyone can access')); +router.get('/admin/users', (c) => c.json({ users: [] })); // Requires admin auth + +export default router; +``` + +## Combining Middleware + +Chain multiple middleware for complex requirements: + +```typescript +import { createRouter } from '@agentuity/runtime'; +import { createMiddleware } from 'hono/factory'; +import { zValidator } from '@hono/zod-validator'; +import { z } from 'zod'; + +const router = createRouter(); + +const rateLimiter = createMiddleware(async (c, next) => { + const ip = c.req.header('x-forwarded-for') || 'unknown'; + const key = `ratelimit:${ip}`; + + const result = await c.kv.get('ratelimit', key); + const count = result.exists ? result.data : 0; + + if (count >= 100) { + return c.json({ error: 'Rate limit exceeded' }, 429); + } + + await c.kv.set('ratelimit', key, count + 1, { ttl: 60 }); + await next(); +}); + +const authMiddleware = createMiddleware(async (c, next) => { + const token = c.req.header('Authorization')?.slice(7); + if (!token) { + return c.json({ error: 'Unauthorized' }, 401); + } + c.set('userId', await validateToken(token)); + await next(); +}); + +const inputSchema = z.object({ + message: z.string().min(1), +}); + +// Chain: rate limit → auth → validation → handler +router.post('/message', + rateLimiter, + authMiddleware, + zValidator('json', inputSchema), + async (c) => { + const userId = c.var.userId; + const { message } = c.req.valid('json'); + + return c.json({ received: true, userId, message }); + } +); + +export default router; +``` + +## Error Handling + +Handle errors gracefully in middleware: + +```typescript +import { createRouter } from '@agentuity/runtime'; +import { createMiddleware } from 'hono/factory'; + +const router = createRouter(); + +const errorHandler = createMiddleware(async (c, next) => { + try { + await next(); + } catch (error) { + c.var.logger.error('Unhandled error', { + error: error instanceof Error ? error.message : String(error), + path: c.req.path, + }); + + return c.json( + { error: 'Internal server error' }, + 500 + ); + } +}); + +router.use('/*', errorHandler); + +router.get('/risky', (c) => { + // If this throws, errorHandler catches it + throw new Error('Something went wrong'); +}); + +export default router; +``` + +## Best Practices + +- **Order matters**: Middleware runs in registration order. Put error handling first, then auth, then validation. +- **Early returns**: Return immediately on failure (don't call `next()`). +- **Share data via context**: Use `c.set('key', value)` to pass data to downstream handlers, access with `c.var.key`. +- **Keep middleware focused**: Each middleware should do one thing. + +## Next Steps + +- [Creating API Routes](/Build/APIs/creating-api-routes): Complete routing reference +- [Calling Agents from APIs](/Build/APIs/calling-agents): Invoke agents from API routes +- [HTTP Routes](/Build/Routes-Triggers/http-routes): Routes in agent directories diff --git a/content/v1/Build/APIs/when-to-use-apis.mdx b/content/v1/Build/APIs/when-to-use-apis.mdx new file mode 100644 index 00000000..16479eae --- /dev/null +++ b/content/v1/Build/APIs/when-to-use-apis.mdx @@ -0,0 +1,201 @@ +--- +title: When to Use APIs +description: Choose between API routes and agents for your endpoints +--- + +Both API routes (`src/apis/`) and agent routes (`src/agents/`) can handle HTTP requests. Both can do the same things; agents add structure (schemas, evals, events) while APIs stay lightweight. + +## Quick Decision Guide + +| Use APIs when... | Use Agents when... | +|------------------|-------------------| +| Simple request/response | Structured processing with validation | +| Health checks, status endpoints | Schema-validated input/output | +| Webhook receivers | Need evaluations or lifecycle events | +| Lightweight data endpoints | Building multi-agent workflows | +| Direct HTTP control needed | Want type-safe agent-to-agent calls | + +## Comparison + +``` +APIs (src/apis/) Agents (src/agents/) +├── route.ts only ├── agent.ts (required) +└── /api/... ├── route.ts (required) + └── /agent/... +``` + +**Both have access to:** +- All storage APIs (`kv`, `vector`, `objectstore`, `stream`) +- Agent calling (`c.agent.myAgent.run()`) +- Background tasks (`c.waitUntil()`) +- Structured logging + +**Only agents have:** +- Schema validation (Zod, Valibot, ArkType) +- Evaluations (`agent.createEval()`) +- Lifecycle events (`addEventListener`) +- Type-safe input/output + +## Example: Health Check (API) + +A health endpoint doesn't need validation, events, or evals: + +```typescript +// src/apis/health/route.ts +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +router.get('/', async (c) => { + // Check dependencies + const dbHealthy = await checkDatabase(); + const cacheHealthy = await checkCache(); + + const status = dbHealthy && cacheHealthy ? 'healthy' : 'degraded'; + const statusCode = status === 'healthy' ? 200 : 503; + + return c.json({ status, db: dbHealthy, cache: cacheHealthy }, statusCode); +}); + +export default router; +``` + +## Example: Webhook Receiver (API) + +Webhooks need fast responses and background processing: + +```typescript +// src/apis/webhooks/stripe/route.ts +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +router.post('/', async (c) => { + const signature = c.req.header('stripe-signature'); + const rawBody = await c.req.text(); + + // Verify webhook signature + if (!verifyStripeSignature(rawBody, signature)) { + return c.json({ error: 'Invalid signature' }, 401); + } + + const event = JSON.parse(rawBody); + + // Respond immediately, process in background + c.waitUntil(async () => { + // Hand off to an agent for complex processing + await c.agent.paymentProcessor.run({ + eventType: event.type, + data: event.data, + }); + }); + + return c.json({ received: true }); +}); + +export default router; +``` + + +Webhook providers (e.g., Stripe, GitHub, Slack) expect fast responses. Use `c.waitUntil()` to acknowledge immediately and process in the background. + + +## Example: Data Endpoint (API) + +Simple CRUD with key-value storage: + +```typescript +// src/apis/settings/route.ts +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +router.get('/:userId', async (c) => { + const userId = c.req.param('userId'); + const result = await c.kv.get('settings', userId); + + if (!result.exists) { + return c.json({ error: 'Not found' }, 404); + } + + return c.json(result.data); +}); + +router.put('/:userId', async (c) => { + const userId = c.req.param('userId'); + const settings = await c.req.json(); + + await c.kv.set('settings', userId, settings); + + return c.json({ updated: true }); +}); + +export default router; +``` + +## Example: Chat Endpoint (Agent) + +LLM-powered endpoints benefit from schemas and evals: + +```typescript +// src/agents/chat/agent.ts +import { createAgent } from '@agentuity/runtime'; +import { generateText } from 'ai'; +import { anthropic } from '@ai-sdk/anthropic'; +import { z } from 'zod'; + +const agent = createAgent({ + schema: { + input: z.object({ + message: z.string().min(1), + userId: z.string(), + }), + output: z.object({ + response: z.string(), + tokensUsed: z.number(), + }), + }, + handler: async (ctx, input) => { + const { text, usage } = await generateText({ + model: anthropic('claude-sonnet-4-5'), + prompt: input.message, + }); + + return { + response: text, + tokensUsed: usage.totalTokens, + }; + }, +}); + +// Quality evaluation +agent.createEval({ + metadata: { name: 'response-quality' }, + handler: async (ctx, input, output) => { + // Evaluate response quality + return { success: true, score: 0.9, metadata: { reason: 'Clear and helpful' } }; + }, +}); + +export default agent; +``` + +## Migrating Between APIs and Agents + +**API to Agent:** When you need validation, evals, or type-safe inter-agent calls. + +1. Create `agent.ts` with schema and handler +2. Move route logic to agent handler +3. Update route to call the agent + +**Agent to API:** When the agent overhead isn't needed. + +1. Create `route.ts` in `src/apis/` +2. Move handler logic directly to route +3. Remove agent file + +## Next Steps + +- [Creating API Routes](/Build/APIs/creating-api-routes): Complete API routing guide +- [Adding Middleware](/Build/APIs/middleware-auth): Authentication patterns +- [Creating Agents](/Build/Agents/creating-agents): When you need schemas and evals diff --git a/content/v1/Build/Frontend/advanced-hooks.mdx b/content/v1/Build/Frontend/advanced-hooks.mdx new file mode 100644 index 00000000..8031a29c --- /dev/null +++ b/content/v1/Build/Frontend/advanced-hooks.mdx @@ -0,0 +1,141 @@ +--- +title: Advanced Hooks +description: Connect to custom WebSocket and SSE endpoints with useWebsocket and useEventStream +--- + +For custom endpoints that aren't agents, use the low-level hooks directly. + + +For calling agents, use `useAgent`, `useAgentWebsocket`, or `useAgentEventStream` instead. These advanced hooks are for custom paths and third-party integrations. + + +## useWebsocket + +Connect to any WebSocket endpoint: + +```tsx +import { useWebsocket } from '@agentuity/react'; +import { useEffect } from 'react'; + +function CustomWebSocket() { + const { connected, send, data, setHandler, close } = useWebsocket< + { action: string }, // Input type + { result: string } // Output type + >('/custom/websocket'); + + // Set a custom message handler + useEffect(() => { + setHandler((message) => { + console.log('Received:', message); + // Process message as needed + }); + }, [setHandler]); + + const handleClick = () => { + send({ action: 'ping' }); + }; + + return ( +
+

Status: {connected ? 'Connected' : 'Disconnected'}

+ + +
+ ); +} +``` + +**Return values:** + +| Property | Type | Description | +|----------|------|-------------| +| `connected` | `boolean` | True when WebSocket is open | +| `send` | `(data: TInput) => void` | Send a message | +| `data` | `TOutput \| undefined` | Last received message | +| `setHandler` | `(fn: (data: TOutput) => void) => void` | Custom message handler | +| `readyState` | `number` | WebSocket ready state | +| `close` | `() => void` | Close the connection | +| `reset` | `() => void` | Clear error state | +| `error` | `Error \| null` | Connection error | + +## useEventStream + +Connect to any Server-Sent Events endpoint: + +```tsx +import { useEventStream } from '@agentuity/react'; +import { useEffect } from 'react'; + +function CustomEventStream() { + const { connected, data, setHandler, close, error } = useEventStream< + { event: string; timestamp: number } + >('/custom/events'); + + useEffect(() => { + setHandler((event) => { + console.log('Event received:', event); + }); + }, [setHandler]); + + if (error) { + return

Error: {error.message}

; + } + + return ( +
+

Stream: {connected ? 'Active' : 'Connecting...'}

+

Last event: {data?.event ?? 'None'}

+ +
+ ); +} +``` + +**Return values:** + +| Property | Type | Description | +|----------|------|-------------| +| `connected` | `boolean` | True when stream is active | +| `data` | `TOutput \| undefined` | Last received event | +| `setHandler` | `(fn: (data: TOutput) => void) => void` | Custom event handler | +| `readyState` | `number` | Connection state | +| `close` | `() => void` | Close the stream | +| `reset` | `() => void` | Clear error state | +| `error` | `Error \| null` | Connection error | + +## Options + +Both hooks accept options for customizing the connection: + +```tsx +const { connected, send } = useWebsocket('/path', { + query: new URLSearchParams({ token: 'abc123' }), + subpath: '/room/1', // Appends to path + signal: abortController.signal, +}); +``` + +## Reconnection Behavior + +Both hooks automatically reconnect when the connection drops using exponential backoff (delays increase between attempts, capped at 30 seconds). + + +Connections are automatically closed when the component unmounts. You don't need to manually call `close()` in a cleanup effect. + + +## When to Use These + +| Use Case | Recommended Hook | +|----------|------------------| +| Calling an agent | `useAgent`, `useAgentWebsocket`, or `useAgentEventStream` | +| Custom API routes | `useWebsocket` or `useEventStream` | +| Third-party WebSocket | `useWebsocket` | +| Custom SSE endpoint | `useEventStream` | + +## Next Steps + +- [React Hooks](/Build/Frontend/react-hooks): Agent-specific hooks for common use cases +- [WebSockets](/Build/Routes-Triggers/websockets): Create WebSocket routes in your agents +- [Server-Sent Events](/Build/Routes-Triggers/sse): Create SSE routes in your agents diff --git a/content/v1/Build/Frontend/deployment-scenarios.mdx b/content/v1/Build/Frontend/deployment-scenarios.mdx new file mode 100644 index 00000000..c2446d83 --- /dev/null +++ b/content/v1/Build/Frontend/deployment-scenarios.mdx @@ -0,0 +1,120 @@ +--- +title: Deployment Scenarios +description: Deploy your frontend alongside agents or separately on Vercel, Netlify, etc. +--- + +Choose where your frontend lives based on your project needs. + +## All-in-One Deployment + +Put your frontend code in `src/web/` and deploy everything together: + +``` +my-project/ +├── src/ +│ ├── agents/ +│ │ └── chat/ +│ │ ├── agent.ts +│ │ └── route.ts +│ └── web/ +│ ├── App.tsx +│ ├── index.html +│ └── main.tsx +└── agentuity.json +``` + +When you run `agentuity deploy`, both your agents and frontend are bundled and deployed together. + +```tsx +// src/web/App.tsx +import { AgentuityProvider, useAgent } from '@agentuity/react'; + +function Chat() { + const { run, data, running } = useAgent('chat'); + // ... your chat UI +} + +export default function App() { + return ( + + + + ); +} +``` + + +When frontend and agents are deployed together, you don't need to set `baseUrl`. The provider automatically connects to the same origin. + + +**Best for:** +- New projects starting from scratch +- Simple deployments with one codebase +- Projects that don't need separate frontend scaling + +## Separate Frontend + +Deploy your frontend on Vercel, Netlify, or any hosting platform while agents run on Agentuity: + +``` +Frontend (e.g., Vercel) Agents (Agentuity) +┌───────────────────────┐ ┌───────────────────────┐ +│ your-app.com │ ────────► │ project.cloud │ +│ React/Next.js │ │ Agent routes │ +└───────────────────────┘ └───────────────────────┘ +``` + +```tsx +// In your React app +import { AgentuityProvider, useAgent } from '@agentuity/react'; + +export default function App() { + return ( + + + + ); +} +``` + +**Best for:** +- Existing projects adding AI agents +- Teams with separate frontend and backend deployments + +## CORS Configuration + +When your frontend and agents are on different domains, configure CORS in `app.ts`: + +```typescript +// app.ts +import { createApp } from '@agentuity/runtime'; + +const app = createApp({ + cors: { + origin: ['https://your-frontend.com', 'http://localhost:3000'], + allowMethods: ['GET', 'POST', 'PUT', 'DELETE'], + allowHeaders: ['Content-Type', 'Authorization'], + }, +}); + +export default app; +``` + + +Without CORS configuration, browsers will block requests from your frontend to your agents when they're on different domains. + + +## Comparison + +| Aspect | All-in-One | Separate Frontend | +|--------|------------|-------------------| +| Deployment | Single `agentuity deploy` | Deploy frontend and agents separately | +| Configuration | Minimal | Requires `baseUrl` and CORS | +| Scaling | Tied together | Independent scaling | +| Best for | New projects | Existing frontends | + +## Next Steps + +- [React Hooks](/Build/Frontend/react-hooks): Call agents from your components +- [Provider Setup](/Build/Frontend/provider-setup): Configure the provider for your environment +- [App Configuration](/Get-Started/app-configuration): CORS and other app-level settings diff --git a/content/v1/Build/Frontend/meta.json b/content/v1/Build/Frontend/meta.json new file mode 100644 index 00000000..236b6607 --- /dev/null +++ b/content/v1/Build/Frontend/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Frontend", + "pages": ["react-hooks", "provider-setup", "deployment-scenarios", "advanced-hooks"] +} diff --git a/content/v1/Build/Frontend/provider-setup.mdx b/content/v1/Build/Frontend/provider-setup.mdx new file mode 100644 index 00000000..60c54ab5 --- /dev/null +++ b/content/v1/Build/Frontend/provider-setup.mdx @@ -0,0 +1,86 @@ +--- +title: Provider Setup +description: Configure AgentuityProvider for local development and production deployments +--- + +Wrap your app in `AgentuityProvider` to connect React hooks to your agents. + +## Basic Setup + +```tsx +import { AgentuityProvider } from '@agentuity/react'; + +export default function App() { + return ( + + + + ); +} +``` + +In development, the provider automatically connects to `http://localhost:3500` where your agents run. + +## Production Configuration + +For deployed applications, set the `baseUrl` to your Agentuity deployment: + +```tsx +import { AgentuityProvider } from '@agentuity/react'; + +export default function App() { + return ( + + + + ); +} +``` + +### Environment-Based Configuration + +Use environment variables to switch between development and production: + +```tsx +import { AgentuityProvider } from '@agentuity/react'; + +const baseUrl = process.env.NODE_ENV === 'production' + ? 'https://your-project.agentuity.cloud' + : undefined; // Falls back to localhost:3500 + +export default function App() { + return ( + + + + ); +} +``` + +For production, set your deployment URL via your framework's environment variable pattern. + +## Provider Props + +| Prop | Type | Default | Description | +|------|------|---------|-------------| +| `baseUrl` | `string` | `http://localhost:3500` | Base URL for agent requests | +| `children` | `ReactNode` | — | Your app components | + +## Type Safety + +When you run `bun dev` or `agentuity dev`, the CLI generates TypeScript types for your agents at `.agentuity/types.d.ts`. This enables autocomplete and type checking for agent names and their input/output schemas. + +```tsx +// TypeScript knows which agents exist and their types +const { run } = useAgent('chat'); // Autocomplete for agent names +await run({ message: 'Hello' }); // Type-checked input +``` + + +Types are regenerated automatically when you add or modify agents. If types seem stale, restart the dev server. + + +## Next Steps + +- [React Hooks](/Build/Frontend/react-hooks): Learn about `useAgent`, `useAgentWebsocket`, and `useAgentEventStream` +- [Deployment Scenarios](/Build/Frontend/deployment-scenarios): Choose where your frontend lives diff --git a/content/v1/Build/Frontend/react-hooks.mdx b/content/v1/Build/Frontend/react-hooks.mdx new file mode 100644 index 00000000..a18d1465 --- /dev/null +++ b/content/v1/Build/Frontend/react-hooks.mdx @@ -0,0 +1,186 @@ +--- +title: React Hooks +description: Call agents from React components with useAgent, useAgentWebsocket, and useAgentEventStream +--- + +Call your agents from React components using type-safe hooks from `@agentuity/react`. + +## Installation + +```bash +npm install @agentuity/react +``` + +## Basic Usage with useAgent + +The `useAgent` hook calls an agent and returns the response: + +```tsx +import { AgentuityProvider, useAgent } from '@agentuity/react'; + +function ChatForm() { + const { run, running, data, error } = useAgent('chat'); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + const formData = new FormData(e.currentTarget); + const message = formData.get('message') as string; + + try { + await run({ message }); + } catch (err) { + // Error is also available via the error state + console.error('Agent call failed:', err); + } + }; + + return ( +
+ + + {error &&

Error: {error.message}

} + {data &&

Response: {data.response}

} +
+ ); +} + +export default function App() { + return ( + + + + ); +} +``` + +**Return values:** + +| Property | Type | Description | +|----------|------|-------------| +| `run` | `(input, options?) => Promise` | Call the agent | +| `running` | `boolean` | True while the request is pending | +| `data` | `TOutput \| undefined` | Last successful response | +| `error` | `Error \| null` | Last error, if any | +| `reset` | `() => void` | Clear error state | + +## Real-Time with useAgentWebsocket + +For bidirectional real-time communication, use `useAgentWebsocket`: + +```tsx +import { useAgentWebsocket } from '@agentuity/react'; +import { useEffect, useState } from 'react'; + +function RealtimeChat() { + const [messages, setMessages] = useState([]); + const { connected, send, data } = useAgentWebsocket('chat'); + + // Handle incoming messages + useEffect(() => { + if (data) { + setMessages((prev) => [...prev, `Agent: ${data}`]); + } + }, [data]); + + const handleSend = (message: string) => { + setMessages((prev) => [...prev, `You: ${message}`]); + send({ message }); + }; + + return ( +
+

Status: {connected ? 'Connected' : 'Connecting...'}

+
    + {messages.map((msg, i) => ( +
  • {msg}
  • + ))} +
+ +
+ ); +} +``` + + +WebSocket connections automatically reconnect with exponential backoff if the connection drops. Messages sent while disconnected are queued and sent when the connection is restored. + + +**Return values:** + +| Property | Type | Description | +|----------|------|-------------| +| `connected` | `boolean` | True when WebSocket is open | +| `send` | `(data: TInput) => void` | Send a message | +| `data` | `TOutput \| undefined` | Last received message | +| `error` | `Error \| null` | Connection or message error | +| `close` | `() => void` | Close the connection | +| `reset` | `() => void` | Clear error state | + +## Streaming with useAgentEventStream + +For one-way streaming from server to client, use Server-Sent Events: + +```tsx +import { useAgentEventStream } from '@agentuity/react'; + +function LiveStatus() { + const { connected, data, error } = useAgentEventStream('status'); + + if (!connected) { + return

Connecting to status feed...

; + } + + if (error) { + return

Error: {error.message}

; + } + + return ( +
+

Live Status: {data?.status ?? 'Waiting for update...'}

+

Last updated: {data?.timestamp ?? '-'}

+
+ ); +} +``` + + +Use `useAgentEventStream` when you only need server-to-client updates (e.g., progress indicators, live dashboards, notifications). For bidirectional communication, use `useAgentWebsocket`. + + +## Choosing the Right Hook + +| Hook | Use Case | Direction | Examples | +|------|----------|-----------|---------| +| `useAgent` | Request/response | One-time | Form submissions, button clicks, etc. | +| `useAgentWebsocket` | Real-time chat | Bidirectional | Chat apps, collaborative editing, etc. | +| `useAgentEventStream` | Live updates | Server → Client | Progress bars, dashboards, notifications, etc. | + +## Request Options + +All hooks accept options for customizing requests: + +```tsx +const { run } = useAgent('myAgent'); + +// Add query parameters +await run(input, { + query: new URLSearchParams({ version: '2' }), +}); + +// Add custom headers +await run(input, { + headers: { 'X-Custom-Header': 'value' }, +}); + +// Cancel request with AbortSignal +const controller = new AbortController(); +await run(input, { signal: controller.signal }); +``` + +## Next Steps + +- [Provider Setup](/Build/Frontend/provider-setup): Configure `AgentuityProvider` for production deployments +- [Advanced Hooks](/Build/Frontend/advanced-hooks): Connect to custom WebSocket and SSE endpoints +- [Deployment Scenarios](/Build/Frontend/deployment-scenarios): Choose where your frontend lives diff --git a/content/v1/Build/Observability/logging.mdx b/content/v1/Build/Observability/logging.mdx new file mode 100644 index 00000000..3b44b9f5 --- /dev/null +++ b/content/v1/Build/Observability/logging.mdx @@ -0,0 +1,162 @@ +--- +title: Logging +description: Structured logging for agents and routes +--- + +Use `ctx.logger` in agents and `c.var.logger` in routes for structured logging. Logs are automatically tied to session IDs for debugging. + +## Log Levels + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const agent = createAgent({ + handler: async (ctx, input) => { + ctx.logger.trace('Verbose debugging info'); + ctx.logger.debug('Debug-level details'); + ctx.logger.info('General information'); + ctx.logger.warn('Warning: potential issue'); + ctx.logger.error('Error occurred', error); + + return { success: true }; + }, +}); +``` + +Levels from most to least verbose: `trace` → `debug` → `info` → `warn` → `error`. The default minimum level is `info`. + +## Structured Logging + +Pass context as a second argument for searchable metadata: + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + const startTime = Date.now(); + + const results = await searchProducts(input.query); + + ctx.logger.info('Search completed', { + query: input.query, + resultCount: results.length, + userId: input.userId, + durationMs: Date.now() - startTime, + }); + + return { results }; + }, +}); +``` + +This creates structured log entries you can filter and search in the Agentuity Console or the CLI. + +## Child Loggers + +Create component-scoped loggers that inherit parent context: + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + // Child logger adds context to all its messages + const dbLogger = ctx.logger.child({ + component: 'database', + requestId: ctx.sessionId, + }); + + dbLogger.debug('Connecting to database'); + dbLogger.info('Query executed', { duration: 45, rows: 10 }); + + // Another child logger for a different component + const embeddingsLogger = ctx.logger.child({ + component: 'embeddings', + documentId: input.documentId, + }); + + embeddingsLogger.info('Generating embeddings', { chunkCount: 12 }); + + return { success: true }; + }, +}); +``` + +Child loggers are useful for tracing operations through different parts of your code. + +## Logging in Routes + +Routes access the logger via `c.var.logger`: + +```typescript +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +router.post('/webhooks/payments', async (c) => { + const eventType = c.req.header('x-webhook-event'); + + c.var.logger.info('Webhook received', { + provider: 'stripe', + eventType, + }); + + const payload = await c.req.json(); + + const result = await c.agent.paymentHandler.run({ + event: eventType, + customerId: payload.customer, + amount: payload.amount, + }); + + c.var.logger.info('Webhook processed', { + eventType, + customerId: payload.customer, + success: result.success, + }); + + return c.json({ received: true }); +}); + +export default router; +``` + +## Configuration + +Set the minimum log level with the `AGENTUITY_LOG_LEVEL` environment variable: + +```bash +# In .env +AGENTUITY_LOG_LEVEL=debug # Show debug and above +``` + +| Level | Shows | +|-------|-------| +| `trace` | trace, debug, info, warn, error | +| `debug` | debug, info, warn, error | +| `info` | info, warn, error (default) | +| `warn` | warn, error | +| `error` | error only | + +## Viewing Logs + +View logs for a specific session using the CLI: + +```bash +# List recent sessions +agentuity cloud session list + +# View logs for a session +agentuity cloud session logs sess_abc123xyz +``` + +Logs are also visible in the Agentuity Console session timeline. See [CLI Reference](/Reference/CLI) for more log viewing options. + +## Best Practices + +- **Use `ctx.logger`**: Always use `ctx.logger` or `c.var.logger` instead of `console.log` for proper log collection +- **Add context**: Include IDs, counts, and timing in structured fields +- **Use appropriate levels**: `info` for normal flow, `warn` for recoverable issues, `error` for failures +- **Create child loggers**: For complex operations, create component-specific loggers + +## Next Steps + +- [Tracing](/Build/Observability/tracing): Track timing and debug performance with OpenTelemetry spans +- [Sessions & Debugging](/Build/Observability/sessions-debugging): Use session IDs to trace issues across logs and spans \ No newline at end of file diff --git a/content/v1/Build/Observability/meta.json b/content/v1/Build/Observability/meta.json new file mode 100644 index 00000000..8cb23a5a --- /dev/null +++ b/content/v1/Build/Observability/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Observability", + "pages": ["logging", "tracing", "sessions-debugging"] +} diff --git a/content/v1/Build/Observability/sessions-debugging.mdx b/content/v1/Build/Observability/sessions-debugging.mdx new file mode 100644 index 00000000..3fd9468c --- /dev/null +++ b/content/v1/Build/Observability/sessions-debugging.mdx @@ -0,0 +1,150 @@ +--- +title: Sessions & Debugging +description: Debug agents using session IDs, CLI commands, and trace timelines +--- + +Every request to your agents gets a unique session ID (`sess_...`). Sessions link logs, traces, and state, making them essential for debugging. + +## Sessions vs Threads + +| Scope | Lifetime | ID Prefix | Use For | +|-------|----------|-----------|---------| +| **Session** | Single request | `sess_` | Debugging, request-scoped state | +| **Thread** | 1 hour (conversation) | `thrd_` | Chat history, user preferences | + +A thread contains multiple sessions. When a user has a multi-turn conversation, each message creates a new session within the same thread. + +## Accessing Session ID + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const agent = createAgent({ + handler: async (ctx, input) => { + // In agents + ctx.logger.info('Processing request', { sessionId: ctx.sessionId }); + + // Thread ID for conversation tracking + ctx.logger.info('Thread context', { threadId: ctx.thread.id }); + + return { sessionId: ctx.sessionId }; + }, +}); +``` + +In routes: + +```typescript +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +router.post('/', async (c) => { + // In routes + c.var.logger.info('Route called', { sessionId: c.var.sessionId }); + + const result = await c.agent.myAgent.run(input); + + return c.json({ ...result, sessionId: c.var.sessionId }); +}); + +export default router; +``` + +## Viewing Session Logs + +Use the CLI to view logs for a specific session: + +```bash +agentuity cloud session logs sess_abc123xyz +``` + +See [CLI Reference](/Reference/CLI) for additional session commands. + +## Including Session ID in Responses + +For easier debugging, include the session ID in error responses: + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + try { + const result = await processRequest(input); + return { success: true, data: result }; + } catch (error) { + ctx.logger.error('Request failed', { + sessionId: ctx.sessionId, + error: error.message, + }); + + return { + success: false, + error: 'Processing failed', + sessionId: ctx.sessionId, // Helpful for debugging + }; + } + }, +}); +``` + +## Linking External Logs + +If you use external logging services, include the session ID so you can connect logs across systems: + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + // Create a logger with session context for external services + const requestLogger = ctx.logger.child({ + sessionId: ctx.sessionId, + threadId: ctx.thread.id, + service: 'webhook-handler', + }); + + requestLogger.info('Processing webhook', { eventType: input.event }); + + // External service call with session context + await externalApi.process({ + ...input, + metadata: { agentuitySessionId: ctx.sessionId }, + }); + + return { success: true }; + }, +}); +``` + +## Session State + +Use session state for request-scoped data that doesn't persist: + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + // Track timing within this request + ctx.session.state.set('startTime', Date.now()); + + const result = await processRequest(input); + + const duration = Date.now() - (ctx.session.state.get('startTime') as number); + ctx.logger.info('Request completed', { durationMs: duration }); + + return result; + }, +}); +``` + +Session state is cleared after the response. For persistent data, use [thread state](/Build/Agents/state-management) or [KV storage](/Build/Storage/key-value). + +## Best Practices + +- **Log the session ID**: Include it in error logs and responses for debugging +- **Use structured logging**: Add context to logs for easier filtering +- **Create child loggers**: Add session context to component-specific loggers +- **Return session ID on errors**: Makes it easy to trace issues back to specific requests + +## Next Steps + +- [Logging](/Build/Observability/logging): Structured logging patterns +- [Tracing](/Build/Observability/tracing): OpenTelemetry spans for performance +- [State Management](/Build/Agents/state-management): Session, thread, and request state diff --git a/content/v1/Build/Observability/tracing.mdx b/content/v1/Build/Observability/tracing.mdx new file mode 100644 index 00000000..07fe8d6b --- /dev/null +++ b/content/v1/Build/Observability/tracing.mdx @@ -0,0 +1,211 @@ +--- +title: Tracing +description: OpenTelemetry spans for performance debugging and operation tracking +--- + +Use `ctx.tracer` in agents and `c.var.tracer` in routes to create OpenTelemetry spans. Spans help you understand timing, track operations through your system, and debug performance issues. + +## Basic Span Pattern + +Wrap operations in spans to track their duration and status: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { SpanStatusCode } from '@opentelemetry/api'; +import { generateText } from 'ai'; +import { openai } from '@ai-sdk/openai'; + +const agent = createAgent({ + handler: async (ctx, input) => { + return ctx.tracer.startActiveSpan('generate-response', async (span) => { + try { + span.setAttribute('model', 'gpt-5-mini'); + span.setAttribute('promptLength', input.prompt.length); + + const { text, usage } = await generateText({ + model: openai('gpt-5-mini'), + prompt: input.prompt, + }); + + span.setAttribute('outputLength', text.length); + span.setAttribute('totalTokens', usage.totalTokens); + span.setStatus({ code: SpanStatusCode.OK }); + + return { response: text }; + } catch (error) { + span.setStatus({ code: SpanStatusCode.ERROR }); + ctx.logger.error('Generation failed', error); + throw error; + } + }); + }, +}); +``` + +Spans automatically end when the handler completes. Always set the status to `OK` or `ERROR`. + +## Adding Context with Attributes + +Use `setAttribute` to add searchable metadata to spans: + +```typescript +return ctx.tracer.startActiveSpan('user-lookup', async (span) => { + span.setAttribute('userId', input.userId); + span.setAttribute('source', 'api'); + span.setAttribute('cached', false); + + const user = await fetchUser(input.userId); + + span.setAttribute('userFound', !!user); + span.setAttribute('accountType', user?.type ?? 'unknown'); + + span.setStatus({ code: SpanStatusCode.OK }); + return user; +}); +``` + +Common attributes: IDs, counts, categories, boolean flags. These appear in trace views and can be filtered. + +## Recording Events + +Use `addEvent` to mark significant moments within a span: + +```typescript +return ctx.tracer.startActiveSpan('data-pipeline', async (span) => { + span.addEvent('pipeline-started', { inputSize: data.length }); + + const validated = await validateData(data); + span.addEvent('validation-complete', { validRecords: validated.length }); + + const enriched = await enrichData(validated); + span.addEvent('enrichment-complete', { enrichedFields: 5 }); + + const stored = await storeData(enriched); + span.addEvent('storage-complete', { recordsStored: stored.count }); + + span.setStatus({ code: SpanStatusCode.OK }); + return { processed: stored.count }; +}); +``` + +Events create a timeline within the span, useful for understanding where time is spent. + +## Nested Spans + +Create child spans for multi-step operations: + +```typescript +return ctx.tracer.startActiveSpan('rag-pipeline', async (parentSpan) => { + try { + parentSpan.setAttribute('query', input.query); + + // Retrieve relevant context + const context = await ctx.tracer.startActiveSpan('retrieve-context', async (span) => { + span.setAttribute('index', 'knowledge-base'); + const results = await ctx.vector.search('docs', { query: input.query, limit: 5 }); + span.setAttribute('resultsFound', results.length); + span.setStatus({ code: SpanStatusCode.OK }); + return results; + }); + + // Rerank results + const ranked = await ctx.tracer.startActiveSpan('rerank-results', async (span) => { + span.setAttribute('inputCount', context.length); + const reranked = await rerankByRelevance(context, input.query); + span.setAttribute('outputCount', reranked.length); + span.setStatus({ code: SpanStatusCode.OK }); + return reranked; + }); + + // Generate answer + const answer = await ctx.tracer.startActiveSpan('generate-answer', async (span) => { + span.setAttribute('model', 'gpt-5-mini'); + span.setAttribute('contextChunks', ranked.length); + const response = await generateWithContext(input.query, ranked); + span.setAttribute('responseLength', response.length); + span.setStatus({ code: SpanStatusCode.OK }); + return response; + }); + + parentSpan.setStatus({ code: SpanStatusCode.OK }); + return { answer, sources: ranked.map(r => r.id) }; + } catch (error) { + parentSpan.setStatus({ code: SpanStatusCode.ERROR }); + throw error; + } +}); +``` + +Nested spans create a parent-child hierarchy in trace views, showing how operations relate. + +## Tracing in Routes + +Routes access the tracer via `c.var.tracer`: + +```typescript +import { createRouter } from '@agentuity/runtime'; +import { SpanStatusCode } from '@opentelemetry/api'; + +const router = createRouter(); + +router.post('/customers/:id/notify', async (c) => { + return c.var.tracer.startActiveSpan('send-notification', async (span) => { + try { + const customerId = c.req.param('id'); + span.setAttribute('customerId', customerId); + + const body = await c.req.json(); + span.setAttribute('notificationType', body.type); + span.setAttribute('channel', body.channel); + + const result = await c.agent.notificationSender.run({ + customerId, + ...body, + }); + + span.setAttribute('delivered', result.success); + span.setStatus({ code: SpanStatusCode.OK }); + return c.json(result); + } catch (error) { + span.setStatus({ code: SpanStatusCode.ERROR }); + throw error; + } + }); +}); + +export default router; +``` + +## Viewing Traces + +View traces for a session using the CLI: + +```bash +# Get session details including trace timeline +agentuity cloud session get sess_abc123xyz +``` + +Traces are also visible in the Agentuity Console, showing the full span hierarchy with timing. See [CLI Reference](/Reference/CLI) for more trace viewing options. + +## When to Use Tracing + +| Scenario | Approach | +|----------|----------| +| Simple operations | Logging is sufficient | +| Multi-step workflows | Create spans for each step | +| Performance debugging | Add spans to identify bottlenecks | +| External API calls | Wrap in spans to track latency | +| Agent-to-agent calls | Spans automatically propagate context | + +## Best Practices + +- **Name spans descriptively**: `generate-summary` not `step-2` +- **Always set status**: `SpanStatusCode.OK` or `SpanStatusCode.ERROR` +- **Add relevant attributes**: IDs, counts, and categories for filtering +- **Use events for milestones**: Mark significant points within long operations +- **Keep spans focused**: One span per logical operation + +## Next Steps + +- [Logging](/Build/Observability/logging): Learn structured logging patterns +- [Sessions & Debugging](/Build/Observability/sessions-debugging): Use session IDs for debugging \ No newline at end of file diff --git a/content/v1/Build/Storage/custom.mdx b/content/v1/Build/Storage/custom.mdx new file mode 100644 index 00000000..728de2b5 --- /dev/null +++ b/content/v1/Build/Storage/custom.mdx @@ -0,0 +1,119 @@ +--- +title: Custom Storage +description: Local development storage and bringing your own storage implementations +--- + +Agentuity provides managed storage in both development and production. You can also bring your own storage implementations. + +## Local Development + +During local development, storage is backed by SQLite and persists between runs. This happens automatically when you're not authenticated, or when you explicitly enable local mode: + +```typescript +import { createApp } from '@agentuity/runtime'; + +const app = await createApp({ + services: { + useLocal: true, // Force local storage even when authenticated + }, +}); +``` + +Local storage data is saved to your project directory, so you can develop with realistic data without affecting production. + +## Production + +When deployed to Agentuity, your agents automatically use managed cloud storage. No configuration required. + +## Custom Storage Implementations + +You can replace any storage type with your own implementation. This is useful when you need to: + +- **Use existing infrastructure**: Connect to your company's Redis, Postgres, or S3 +- **Meet compliance requirements**: Keep data in specific regions or systems +- **Self-host**: Run entirely on your own infrastructure + +Pass your implementation to `createApp()`: + +```typescript +import { createApp } from '@agentuity/runtime'; +import { MyRedisKV, MyPineconeVector } from './my-storage'; + +const app = await createApp({ + services: { + keyvalue: new MyRedisKV(), + vector: new MyPineconeVector(), + // object and stream use Agentuity defaults + }, +}); +``` + +### Storage Interfaces + +Your implementation must satisfy the interface for each storage type. When your agent calls `ctx.kv.get()`, the SDK calls your implementation's `get()` method. + +These interfaces are exported from `@agentuity/core`: + +#### KeyValueStorage + +```typescript +interface KeyValueStorage { + get(name: string, key: string): Promise>; + set(name: string, key: string, value: T, params?: { ttl?: number; contentType?: string }): Promise; + delete(name: string, key: string): Promise; + getStats(name: string): Promise; + getNamespaces(): Promise; + search(name: string, keyword: string): Promise>>; + getKeys(name: string): Promise; +} +``` + +#### VectorStorage + +```typescript +interface VectorStorage { + upsert(name: string, ...documents: VectorUpsertParams[]): Promise; + get(name: string, key: string): Promise>; + getMany(name: string, ...keys: string[]): Promise>>; + search(name: string, params: VectorSearchParams): Promise[]>; + delete(name: string, ...keys: string[]): Promise; + exists(name: string): Promise; +} +``` + +#### ObjectStorage + +```typescript +interface ObjectStorage { + get(bucket: string, key: string): Promise; + put(bucket: string, key: string, data: Uint8Array | ArrayBuffer | ReadableStream, params?: ObjectStorePutParams): Promise; + delete(bucket: string, key: string): Promise; + createPublicURL(bucket: string, key: string, params?: CreatePublicURLParams): Promise; + listBuckets(): Promise; + listObjects(bucket: string, options?: { prefix?: string; limit?: number }): Promise; + headObject(bucket: string, key: string): Promise; +} +``` + +#### StreamStorage + +```typescript +interface StreamStorage { + create(name: string, props?: CreateStreamProps): Promise; + get(id: string): Promise; + download(id: string): Promise>; + list(params?: ListStreamsParams): Promise; + delete(id: string): Promise; +} +``` + + +Custom storage must be accessible from Agentuity's infrastructure when deployed. Ensure your storage endpoints are reachable and properly authenticated. + + +## Next Steps + +- [Key-Value Storage](/Build/Storage/key-value): Fast caching and configuration +- [Vector Storage](/Build/Storage/vector): Semantic search and embeddings +- [Object Storage](/Build/Storage/object): File and media storage +- [Durable Streams](/Build/Storage/durable-streams): Streaming large data exports diff --git a/content/v1/Build/Storage/durable-streams.mdx b/content/v1/Build/Storage/durable-streams.mdx new file mode 100644 index 00000000..ac8bf19e --- /dev/null +++ b/content/v1/Build/Storage/durable-streams.mdx @@ -0,0 +1,284 @@ +--- +title: Durable Streams +description: Streaming storage for large exports, audit logs, and real-time data +--- + +Durable streams provide streaming storage for large data exports, audit logs, and real-time processing. Streams follow a **write-once, read-many** pattern: once data is written and the stream is closed, the content is immutable and can be accessed via a permanent public URL. + +## When to Use Durable Streams + +| Storage Type | Best For | +|--------------|----------| +| **Durable Streams** | Large exports, audit logs, streaming data, batch processing | +| [Key-Value](/Build/Storage/key-value) | Fast lookups, caching, configuration | +| [Vector](/Build/Storage/vector) | Semantic search, embeddings, RAG | +| [Object](/Build/Storage/object) | Files, images, documents, media | + +**Use streams when you need to:** +- Export large datasets incrementally +- Create audit logs while streaming to clients +- Process data in chunks without holding everything in memory +- Return a URL immediately while data writes in the background + +## Creating Streams + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const agent = createAgent({ + handler: async (ctx, input) => { + const stream = await ctx.stream.create('export', { + contentType: 'text/csv', + compress: true, // optional gzip compression + metadata: { userId: input.userId }, + }); + + // Stream is ready immediately + ctx.logger.info('Stream created', { + id: stream.id, + url: stream.url, + }); + + return { streamId: stream.id, streamUrl: stream.url }; + }, +}); +``` + +**Options:** +- `contentType`: MIME type for the stream content (e.g., `text/csv`, `application/json`) +- `compress`: Enable gzip compression for smaller storage and faster transfers +- `metadata`: Key-value pairs for tracking stream context (user IDs, timestamps, etc.) + +## Writing Data + +Write data incrementally, then close the stream: + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + const stream = await ctx.stream.create('export', { + contentType: 'text/csv', + }); + + // Write header + await stream.write('Name,Email,Created\n'); + + // Write rows + for (const user of input.users) { + await stream.write(`${user.name},${user.email},${user.created}\n`); + } + + // Close when done + await stream.close(); + + ctx.logger.info('Export complete', { + bytesWritten: stream.bytesWritten, + }); + + return { url: stream.url }; + }, +}); +``` + + +Streams must be closed manually with `stream.close()`. They do not auto-close. Failing to close a stream leaves it in an incomplete state. + + +## Background Processing + +Use `ctx.waitUntil()` to write data in the background while returning immediately: + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + const stream = await ctx.stream.create('report', { + contentType: 'application/json', + }); + + // Return URL immediately + const response = { streamUrl: stream.url }; + + // Process in background + ctx.waitUntil(async () => { + const data = await fetchLargeDataset(input.query); + + await stream.write(JSON.stringify(data, null, 2)); + await stream.close(); + + ctx.logger.info('Background export complete'); + }); + + return response; + }, +}); +``` + +## Listing and Deleting Streams + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + // List streams with optional filtering + const result = await ctx.stream.list({ + name: 'export', // filter by stream name + limit: 10, + }); + + ctx.logger.info('Found streams', { + total: result.total, + count: result.streams.length, + }); + + // Delete a stream + await ctx.stream.delete(input.streamId); + + return { streams: result.streams }; + }, +}); +``` + +## Dual Stream Pattern + +Create two streams simultaneously: one for the client response, one for audit logging. + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { streamText } from 'ai'; +import { openai } from '@ai-sdk/openai'; + +const agent = createAgent({ + handler: async (ctx, input) => { + // Create two streams + const mainStream = await ctx.stream.create('output', { + contentType: 'text/plain', + }); + const auditStream = await ctx.stream.create('audit', { + contentType: 'application/json', + metadata: { userId: input.userId }, + }); + + // Return main stream URL immediately + const response = { streamUrl: mainStream.url }; + + // Process both streams in background + ctx.waitUntil(async () => { + const { textStream } = streamText({ + model: openai('gpt-5-mini'), + prompt: input.message, + }); + + const chunks: string[] = []; + for await (const chunk of textStream) { + // Write to client stream + await mainStream.write(chunk); + chunks.push(chunk); + } + + // Write audit log + await auditStream.write(JSON.stringify({ + timestamp: new Date().toISOString(), + userId: input.userId, + prompt: input.message, + response: chunks.join(''), + })); + + await mainStream.close(); + await auditStream.close(); + }); + + return response; + }, +}); +``` + +## Content Moderation While Streaming + +Buffer and evaluate content in real-time using an LLM-as-a-judge pattern: + +```typescript +import { generateObject, streamText } from 'ai'; +import { openai } from '@ai-sdk/openai'; +import { groq } from '@ai-sdk/groq'; +import { z } from 'zod'; + +const agent = createAgent({ + handler: async (ctx, input) => { + const stream = await ctx.stream.create('moderated', { + contentType: 'text/plain', + }); + + ctx.waitUntil(async () => { + const { textStream } = streamText({ + model: openai('gpt-5-mini'), + prompt: input.message, + }); + + let buffer = ''; + const sentenceEnd = /[.!?]\s/; + + for await (const chunk of textStream) { + buffer += chunk; + + // Check complete sentences + if (sentenceEnd.test(buffer)) { + const isAppropriate = await moderateContent(buffer); + + if (isAppropriate) { + await stream.write(buffer); + } else { + ctx.logger.warn('Content blocked', { content: buffer }); + await stream.write('[Content filtered]'); + } + buffer = ''; + } + } + + // Flush remaining buffer + if (buffer) { + await stream.write(buffer); + } + + await stream.close(); + }); + + return { streamUrl: stream.url }; + }, +}); + +// Use Groq for low-latency moderation +async function moderateContent(text: string): Promise { + const { object } = await generateObject({ + model: groq('openai/gpt-oss-20b'), + schema: z.object({ + safe: z.boolean(), + reason: z.string().optional(), + }), + prompt: `Is this content appropriate? Respond with safe=true if appropriate, safe=false if it contains harmful content.\n\nContent: "${text}"`, + }); + + return object.safe; +} +``` + +## Stream Properties + +| Property | Description | +|----------|-------------| +| `stream.id` | Unique stream identifier | +| `stream.url` | Public URL to access the stream | +| `stream.bytesWritten` | Total bytes written so far | + +## Best Practices + +- **Use compression**: Enable `compress: true` for large text exports to reduce storage and transfer time +- **Return URLs early**: Use `ctx.waitUntil()` to return the stream URL while writing in the background +- **Clean up**: Delete streams after they're no longer needed to free storage +- **Set content types**: Always specify the correct MIME type for proper browser handling + +## Next Steps + +- [Key-Value Storage](/Build/Storage/key-value): Fast caching and configuration +- [Vector Storage](/Build/Storage/vector): Semantic search and embeddings +- [Object Storage](/Build/Storage/object): File and media storage +- [Custom Storage](/Build/Storage/custom): Bring your own storage implementations +- [Streaming Responses](/Build/Agents/streaming-responses): HTTP streaming patterns diff --git a/content/v1/Build/Storage/key-value.mdx b/content/v1/Build/Storage/key-value.mdx new file mode 100644 index 00000000..23a00df1 --- /dev/null +++ b/content/v1/Build/Storage/key-value.mdx @@ -0,0 +1,208 @@ +--- +title: Key-Value Storage +description: Fast, ephemeral storage for caching, session data, and configuration +--- + +Key-value ("KV") storage provides fast data access for agents. Use it for caching, configuration, rate limiting, and data that needs quick lookups. + +## When to Use Key-Value Storage + +| Storage Type | Best For | +|--------------|----------| +| **Key-Value** | Fast lookups, caching, configuration, rate limits | +| [Vector](/Build/Storage/vector) | Semantic search, embeddings, RAG | +| [Object](/Build/Storage/object) | Files, images, documents, media | +| [Durable Streams](/Build/Storage/durable-streams) | Large exports, audit logs, real-time data | + + +Use built-in state (`ctx.state`, `ctx.thread.state`, `ctx.session.state`) for data tied to active requests and conversations. Use KV when you need custom TTL, *persistent data across sessions*, or *shared state across agents*. + + +## Basic Operations + +Access key-value storage through `ctx.kv` in agents or `c.kv` in routes. Buckets are auto-created on first use. + +### Storing Data + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const agent = createAgent({ + handler: async (ctx, input) => { + // Store with optional TTL (minimum 60 seconds) + await ctx.kv.set('cache', 'api-response', responseData, { + ttl: 3600, // expires in 1 hour + contentType: 'application/json', + }); + + // Store without TTL (persists indefinitely) + await ctx.kv.set('config', 'feature-flags', { + darkMode: true, + betaFeatures: false, + }); + + return { success: true }; + }, +}); +``` + +### Retrieving Data + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + const result = await ctx.kv.get('cache', 'api-response'); + + if (result.exists) { + ctx.logger.info('Cache hit', { contentType: result.contentType }); + return { data: result.data }; + } + + ctx.logger.info('Cache miss'); + return { data: null }; + }, +}); +``` + +### Deleting Data + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + await ctx.kv.delete('sessions', input.sessionId); + return { deleted: true }; + }, +}); +``` + +## Type Safety + +Use generics for type-safe data access: + +```typescript +interface UserPreferences { + theme: 'light' | 'dark'; + language: string; + notifications: boolean; +} + +const agent = createAgent({ + handler: async (ctx, input) => { + const result = await ctx.kv.get('prefs', input.userId); + + if (result.exists) { + // TypeScript knows the shape of result.data + const theme = result.data.theme; // Type: 'light' | 'dark' + return { theme }; + } + + return { theme: 'light' }; // default + }, +}); +``` + +## Additional Methods + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + // Search keys by keyword + const matches = await ctx.kv.search('cache', 'user-'); + + // List all keys in a bucket + const keys = await ctx.kv.getKeys('cache'); + + // List all buckets + const buckets = await ctx.kv.getNamespaces(); + + // Get bucket statistics + const stats = await ctx.kv.getStats('cache'); + + return { keys, buckets, stats }; + }, +}); +``` + +## TTL Strategy + +Keys persist indefinitely by default. Use TTL for temporary data: + +| Data Type | Suggested TTL | +|-----------|---------------| +| API cache | 5-60 minutes (300-3600s) | +| Session data | 24-48 hours (86400-172800s) | +| Rate limit counters | Until period reset | +| Feature flags | No TTL (persistent) | + +```typescript +interface UserSession { + userId: string; + email: string; + loginAt: string; + preferences: { theme: string }; +} + +const agent = createAgent({ + handler: async (ctx, input) => { + const sessionKey = `session:${input.token}`; + + // Check for existing session + const existing = await ctx.kv.get('sessions', sessionKey); + if (existing.exists) { + return { session: existing.data }; + } + + // Create new session with 24-hour TTL + const session: UserSession = { + userId: input.userId, + email: input.email, + loginAt: new Date().toISOString(), + preferences: { theme: 'light' }, + }; + + await ctx.kv.set('sessions', sessionKey, session, { + ttl: 86400, // 24 hours + }); + + return { session }; + }, +}); +``` + +## Using in Routes + +Routes have the same KV access via `c.kv`: + +```typescript +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +router.get('/session/:id', async (c) => { + const sessionId = c.req.param('id'); + const result = await c.kv.get('sessions', sessionId); + + if (!result.exists) { + return c.json({ error: 'Session not found' }, 404); + } + + return c.json({ session: result.data }); +}); + +export default router; +``` + +## Best Practices + +- **Use descriptive keys**: `user:{userId}:prefs` instead of `u123` +- **Set appropriate TTLs**: Prevent storage bloat with expiring cache entries +- **Handle missing keys**: Always check `result.exists` before accessing data +- **Keep values small**: KV is optimized for small-to-medium values; use Object Storage for large files + +## Next Steps + +- [Vector Storage](/Build/Storage/vector): Semantic search and embeddings +- [Object Storage](/Build/Storage/object): File and media storage +- [Durable Streams](/Build/Storage/durable-streams): Large data exports +- [Custom Storage](/Build/Storage/custom): Bring your own storage implementations +- [State Management](/Build/Agents/state-management): Built-in request/thread/session state diff --git a/content/v1/Build/Storage/meta.json b/content/v1/Build/Storage/meta.json new file mode 100644 index 00000000..b26229ed --- /dev/null +++ b/content/v1/Build/Storage/meta.json @@ -0,0 +1,4 @@ +{ + "title": "Storage", + "pages": ["key-value", "vector", "object", "durable-streams"] +} diff --git a/content/v1/Build/Storage/object.mdx b/content/v1/Build/Storage/object.mdx new file mode 100644 index 00000000..8c798b33 --- /dev/null +++ b/content/v1/Build/Storage/object.mdx @@ -0,0 +1,189 @@ +--- +title: Object Storage +description: Durable file storage for documents, images, and binary content +--- + +Object storage provides durable file storage for agents. Use it for documents, images, media, and any binary content that needs to persist. + +## When to Use Object Storage + +| Storage Type | Best For | +|--------------|----------| +| **Object** | Files, images, documents, media, backups | +| [Key-Value](/Build/Storage/key-value) | Fast lookups, caching, configuration | +| [Vector](/Build/Storage/vector) | Semantic search, embeddings, RAG | +| [Durable Streams](/Build/Storage/durable-streams) | Large exports, audit logs | + +## Storing Objects + +Access object storage through `ctx.objectstore` in agents or `c.objectstore` in routes. Buckets are auto-created on first use. + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const agent = createAgent({ + handler: async (ctx, input) => { + // Store text file + const textData = new TextEncoder().encode('Hello, World!'); + await ctx.objectstore.put('uploads', 'hello.txt', textData, { + contentType: 'text/plain', + }); + + // Store with full options + await ctx.objectstore.put('documents', 'report.pdf', pdfData, { + contentType: 'application/pdf', + contentDisposition: 'attachment; filename="report.pdf"', + cacheControl: 'max-age=3600', + metadata: { + 'uploaded-by': input.userId, + 'processed': 'false', + }, + }); + + return { success: true }; + }, +}); +``` + +**Supported data types**: `Uint8Array`, `ArrayBuffer`, `ReadableStream` + +## Retrieving Objects + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + const result = await ctx.objectstore.get('uploads', 'hello.txt'); + + if (result.exists) { + // Data is Uint8Array, convert to text if needed + const text = new TextDecoder().decode(result.data); + ctx.logger.info('File content', { + size: result.data.byteLength, + contentType: result.contentType, + }); + return { content: text }; + } + + return { error: 'File not found' }; + }, +}); +``` + +## Public URLs + +Generate time-limited signed URLs for sharing files: + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + // Create URL valid for 1 hour + const url = await ctx.objectstore.createPublicURL('documents', 'report.pdf', { + expiresDuration: 3600000, // milliseconds + }); + + return { downloadUrl: url }; + }, +}); +``` + + +Always use time-limited URLs for sensitive data. The minimum expiration is 1 minute (60000ms), and the default is 1 hour. + + +## Deleting Objects + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + // Returns true if deleted, false if not found + const deleted = await ctx.objectstore.delete('uploads', 'temp-file.txt'); + + if (deleted) { + ctx.logger.info('File deleted'); + } + + return { deleted }; + }, +}); +``` + +## Listing and Metadata + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + // List all buckets + const buckets = await ctx.objectstore.listBuckets(); + + // List objects in a bucket with optional filtering + const objects = await ctx.objectstore.listObjects('uploads', { + prefix: 'users/', // filter by prefix + limit: 100, // max results + }); + + // Get object metadata without downloading content + const metadata = await ctx.objectstore.headObject('uploads', 'large-file.zip'); + + return { buckets, objects, metadata }; + }, +}); +``` + +## Using in Routes + +Routes have the same access via `c.objectstore`: + +```typescript +import { createRouter } from '@agentuity/runtime'; + +const router = createRouter(); + +// File upload endpoint +router.post('/upload/:filename', async (c) => { + const filename = c.req.param('filename'); + const contentType = c.req.header('content-type') || 'application/octet-stream'; + + const arrayBuffer = await c.req.arrayBuffer(); + const data = new Uint8Array(arrayBuffer); + + await c.objectstore.put('uploads', filename, data, { contentType }); + + return c.json({ + success: true, + size: data.byteLength, + }); +}); + +// File download endpoint +router.get('/download/:filename', async (c) => { + const filename = c.req.param('filename'); + const result = await c.objectstore.get('uploads', filename); + + if (!result.exists) { + return c.json({ error: 'File not found' }, 404); + } + + return c.body(result.data, { + headers: { + 'content-type': result.contentType, + 'content-disposition': `attachment; filename="${filename}"`, + }, + }); +}); + +export default router; +``` + +## Best Practices + +- **Organize with paths**: Use hierarchical keys like `users/{userId}/avatar.jpg` +- **Set content types**: Specify MIME types like `image/jpeg`, `application/pdf`, `text/plain` for proper browser handling. If omitted, defaults to `application/octet-stream` +- **Use short-lived URLs**: For sensitive data, use minimal expiration times +- **Store metadata**: Use the metadata option to track upload info, processing status, etc. + +## Next Steps + +- [Key-Value Storage](/Build/Storage/key-value): Fast caching and configuration +- [Vector Storage](/Build/Storage/vector): Semantic search and embeddings +- [Durable Streams](/Build/Storage/durable-streams): Streaming large data exports +- [Custom Storage](/Build/Storage/custom): Bring your own storage implementations diff --git a/content/v1/Build/Storage/vector.mdx b/content/v1/Build/Storage/vector.mdx new file mode 100644 index 00000000..fc36200d --- /dev/null +++ b/content/v1/Build/Storage/vector.mdx @@ -0,0 +1,256 @@ +--- +title: Vector Storage +description: Semantic search and retrieval for knowledge bases and RAG systems +--- + +Vector storage enables semantic search, allowing agents to find information by *meaning* rather than keywords. Use it for knowledge bases, RAG systems, recommendations, and persistent agent memory. + +## When to Use Vector Storage + +| Storage Type | Best For | +|--------------|----------| +| **Vector** | Semantic search, embeddings, RAG, recommendations | +| [Key-Value](/Build/Storage/key-value) | Fast lookups, caching, configuration | +| [Object](/Build/Storage/object) | Files, images, documents, media | +| [Durable Streams](/Build/Storage/durable-streams) | Large exports, audit logs | + +## Upserting Documents + +Store documents with automatic embedding generation. The `upsert` operation is idempotent: using an existing key updates the vector rather than creating a duplicate. + +```typescript +import { createAgent } from '@agentuity/runtime'; + +const agent = createAgent({ + handler: async (ctx, input) => { + // Upsert with text (auto-generates embeddings) + const results = await ctx.vector.upsert('knowledge-base', + { + key: 'doc-1', + document: 'Agentuity is an agent-native cloud platform', + metadata: { category: 'platform', source: 'docs' }, + }, + { + key: 'doc-2', + document: 'Vector storage enables semantic search capabilities', + metadata: { category: 'features', source: 'docs' }, + } + ); + + // Returns: [{ key: 'doc-1', id: 'internal-id' }, ...] + return { inserted: results.length }; + }, +}); +``` + +**With pre-computed embeddings:** + +```typescript +await ctx.vector.upsert('custom-embeddings', { + key: 'embedding-1', + embeddings: [0.1, 0.2, 0.3, 0.4, ...], + metadata: { source: 'external' }, +}); +``` + + +## Searching + +Find semantically similar documents: + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + const results = await ctx.vector.search('knowledge-base', { + query: 'What is an AI agent?', + limit: 5, + similarity: 0.7, // minimum similarity threshold + metadata: { category: 'platform' }, // filter by metadata + }); + + // Each result includes: id, key, similarity, metadata + return { + results: results.map(r => ({ + key: r.key, + similarity: r.similarity, + title: r.metadata?.title, + })), + }; + }, +}); +``` + +## Direct Retrieval + +### get() - Single Item + +Retrieve a specific vector by key without similarity search: + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + const result = await ctx.vector.get('knowledge-base', 'doc-1'); + + if (result.exists) { + return { + id: result.data.id, + key: result.data.key, + metadata: result.data.metadata, + }; + } + + return { error: 'Document not found' }; + }, +}); +``` + +### getMany() - Batch Retrieval + +Retrieve multiple vectors efficiently: + +```typescript +const agent = createAgent({ + handler: async (ctx, input) => { + const keys = ['doc-1', 'doc-2', 'doc-3']; + const resultMap = await ctx.vector.getMany('knowledge-base', ...keys); + + // resultMap is Map + return { + found: resultMap.size, + documents: Array.from(resultMap.values()).map(doc => ({ + key: doc.key, + content: doc.document, + })), + }; + }, +}); +``` + +### exists() - Check Namespace + +```typescript +const hasData = await ctx.vector.exists('knowledge-base'); +if (!hasData) { + return { error: 'Knowledge base not initialized' }; +} +``` + +## Deleting Vectors + +```typescript +// Delete single vector +await ctx.vector.delete('knowledge-base', 'doc-1'); + +// Delete multiple vectors, returns count deleted +const count = await ctx.vector.delete('knowledge-base', 'doc-1', 'doc-2', 'doc-3'); +``` + +## Type Safety + +Use generics for type-safe metadata: + +```typescript +interface DocumentMetadata { + title: string; + category: 'guide' | 'api' | 'tutorial'; + author: string; +} + +const agent = createAgent({ + handler: async (ctx, input) => { + // Type-safe upsert + await ctx.vector.upsert('docs', { + key: 'guide-1', + document: 'Getting started with agents', + metadata: { + title: 'Getting Started', + category: 'guide', + author: 'team', + }, + }); + + // Type-safe search + const results = await ctx.vector.search('docs', { + query: input.question, + }); + + // TypeScript knows metadata shape + const titles = results.map(r => r.metadata?.title); + + return { titles }; + }, +}); +``` + +## Simple RAG Example + +Search for relevant context and generate an informed response: + +```typescript +import { createAgent } from '@agentuity/runtime'; +import { generateText } from 'ai'; +import { openai } from '@ai-sdk/openai'; +import { z } from 'zod'; + +const ragAgent = createAgent({ + schema: { + input: z.object({ question: z.string() }), + output: z.object({ + answer: z.string(), + sources: z.array(z.string()), + }), + }, + handler: async (ctx, input) => { + // Search for relevant documents + const results = await ctx.vector.search('knowledge-base', { + query: input.question, + limit: 3, + similarity: 0.7, + }); + + if (results.length === 0) { + return { + answer: "I couldn't find relevant information.", + sources: [], + }; + } + + // Build context from results + const context = results + .map(r => r.metadata?.content || '') + .join('\n\n'); + + // Generate response using context + const { text } = await generateText({ + model: openai('gpt-5-mini'), + prompt: `Answer based on this context:\n\n${context}\n\nQuestion: ${input.question}`, + }); + + return { + answer: text, + sources: results.map(r => r.key), + }; + }, +}); +``` + +## Troubleshooting + +- **Empty results**: Lower your similarity threshold (try 0.5 instead of 0.8) or check metadata filters +- **Duplicates**: Ensure consistent key naming; upsert with same key updates rather than duplicates +- **Poor relevance**: Results with similarity < 0.7 may be weak matches; filter post-search if needed + +## Best Practices + +- **Include context in documents**: Store enough text so documents are meaningful when retrieved +- **Use descriptive metadata**: Include title, category, tags for filtering and identification +- **Batch upserts**: Insert documents in batches of 100-500 for better performance +- **Combine get + search**: Use `search` for finding, `getMany` for fetching full details + +## Next Steps + +- [Key-Value Storage](/Build/Storage/key-value): Fast caching and configuration +- [Object Storage](/Build/Storage/object): File and media storage +- [Durable Streams](/Build/Storage/durable-streams): Streaming large data exports +- [Custom Storage](/Build/Storage/custom): Bring your own storage implementations +- [Evaluations](/Build/Agents/evaluations): Quality checks for RAG systems diff --git a/content/v1/Build/meta.json b/content/v1/Build/meta.json index 03c0f0f8..c9b9d9f4 100644 --- a/content/v1/Build/meta.json +++ b/content/v1/Build/meta.json @@ -1,4 +1,4 @@ { "title": "Build", - "pages": ["Agents", "Routes-Triggers"] + "pages": ["Agents", "Routes-Triggers", "APIs", "Frontend", "Storage", "Observability"] } diff --git a/content/v1/Guides/key-value-storage.mdx b/content/v1/Guides/key-value-storage.mdx deleted file mode 100644 index 74d4c2f3..00000000 --- a/content/v1/Guides/key-value-storage.mdx +++ /dev/null @@ -1,277 +0,0 @@ ---- -title: Key-Value Storage -description: Fast, ephemeral storage for agent state, configuration, and caching ---- - -# Key-Value Storage - -Key-value storage provides fast, ephemeral data access for agents. Use it for session state, configuration, caching, and temporary data that needs quick lookups. - -## When to Use Key-Value Storage - -Key-value storage is your go-to solution for fast, ephemeral data that agents need to access quickly. Think of it as your agent's short-term memory - perfect for session state, configuration, caching, and temporary data. - -Choose the right storage for your use case: - -- **Key-Value**: Fast lookups, simple data, temporary state -- **Vector Storage**: Semantic search, embeddings, similarity matching -- **Object Storage**: Large files, media, backups - -## Common Use Cases - -- **Persistent User Data**: Store user profiles, "remember me" tokens, and preferences that persist beyond active session lifetimes (use `c.session.state` for active session data - see [Sessions and Threads](/Guides/sessions-threads)) -- **Configuration Storage**: Keep agent-specific settings, feature flags, and runtime configuration that can be updated without redeployment -- **Caching**: Cache expensive computation results, API responses, or frequently accessed data to improve agent performance -- **Inter-Agent Communication**: Share state between agents working together on complex workflows -- **Rate Limiting**: Track API usage, request counts, and implement throttling mechanisms - - -v1 provides built-in state management (`c.state`, `ctx.thread.state`, `c.session.state`) for data tied to active requests, conversations, and sessions. Use KV when you need: - -- **Custom TTL** longer than session/thread lifetimes -- **Persistent data** across sessions (user profiles, settings) -- **Shared state** across multiple sessions or agents (global config, rate limits) - -See [Sessions and Threads](/Guides/sessions-threads) for built-in state management. - - -## Creating Key-Value Storage - -You can create key-value storage either through the Cloud Console or programmatically in your agent code. - -### Via Cloud Console - -Navigate to **Services > Key Value** and click **Create Storage**. Choose a descriptive name that reflects the storage purpose (e.g., `user-sessions`, `agent-config`, `api-cache`). - - - -### Via SDK - -The SDK automatically creates key-value storage when you first access it: - -```typescript -import { createAgent } from '@agentuity/runtime'; - -const agent = createAgent({ - handler: async (ctx, input) => { - // Buckets are auto-created if they don't exist - await ctx.kv.set('user-sessions', 'user-123', { - lastSeen: new Date().toISOString(), - preferences: { theme: 'dark' } - }); - - return { message: 'Session stored' }; - } -}); -``` - -## Working with Key-Value Storage - -The key-value API provides three core operations: `get`, `set`, and `delete`. All operations are asynchronous and support various data types. - -The first parameter in all operations is the storage bucket name (also called "name" or "namespace"). Buckets are automatically created when you first use them. - -### Storing Data - -Store strings, objects, or binary data. Keys persist indefinitely by default, or you can set a TTL (time-to-live) for automatic expiration: - -```typescript -const agent = createAgent({ - handler: async (ctx, input) => { - // Simple store - await ctx.kv.set('cache', 'api-response', responseData); - - // Store an object - await ctx.kv.set('user-prefs', input.userId, { - language: 'en', - timezone: 'UTC' - }); - - // Store with TTL (expires after 1 hour) - await ctx.kv.set('sessions', input.sessionId, userData, { - ttl: 3600 // seconds - }); - - // Store feature flags (no TTL - persistent config) - await ctx.kv.set('feature-flags', 'beta-features', { - darkMode: true, - aiAssistant: false, - newDashboard: true - }); - - return { success: true }; - } -}); -``` - -### Retrieving Data - -Retrieve stored values with automatic deserialization: - -```typescript -const agent = createAgent({ - handler: async (ctx, input) => { - // Get a value - const result = await ctx.kv.get('user-prefs', input.userId); - - if (result.exists) { - // Data is already deserialized - const preferences = result.data; - ctx.logger.info('User preferences:', preferences); - } else { - ctx.logger.info('No preferences found for user'); - } - - // Handle missing keys gracefully - const configResult = await c.kv.get('config', 'app-settings'); - const config = configResult.exists ? configResult.data : defaultConfig; - - return { config }; - } -}); -``` - -**Type Safety with TypeScript:** - -Use type parameters for full type safety: - -```typescript -interface UserPreferences { - language: string; - timezone: string; - theme: 'light' | 'dark'; -} - -const agent = createAgent({ - handler: async (ctx, input) => { - // Specify type for type-safe access - const result = await ctx.kv.get('user-prefs', input.userId); - - if (result.exists) { - // TypeScript knows the shape of result.data - const theme = result.data.theme; // Type: 'light' | 'dark' - const language = result.data.language; // Type: string - } - - return { success: true }; - } -}); -``` - -### Deleting Data - -Remove keys when they're no longer needed: - -```typescript -const agent = createAgent({ - handler: async (ctx, input) => { - // Delete a single key - await ctx.kv.delete('sessions', input.sessionId); - - ctx.logger.info('Session deleted successfully'); - - return { success: true }; - } -}); -``` - -## Best Practices - -### Key Naming Conventions - -Use hierarchical, descriptive keys to organize your data: -- `user:{userId}:preferences` -- `session:{sessionId}:data` -- `cache:api:{endpoint}:{params}` - -### Error Handling - -Always handle potential storage errors gracefully: - -```typescript -const agent = createAgent({ - handler: async (ctx, input) => { - try { - const result = await ctx.kv.get('config', 'settings'); - - if (!result.exists) { - // Initialize with defaults - await ctx.kv.set('config', 'settings', defaultSettings); - } - - return { config: result.exists ? result.data : defaultSettings }; - } catch (error) { - ctx.logger.error('Storage error:', error); - - // Fall back to in-memory defaults - return { config: defaultSettings }; - } - } -}); -``` - -### TTL Strategy - -Keys persist indefinitely by default unless you specify a TTL (time-to-live) value. When you do specify a TTL, the minimum allowed value is 60 seconds. - -Use TTL for temporary data to prevent storage bloat: -- **Session data**: 24-48 hours (86400-172800 seconds) -- **API cache**: 5-60 minutes (300-3600 seconds) -- **Rate-limiting counters**: Until period reset -- **Permanent config**: No TTL (keys persist forever) - -Example with appropriate TTLs: - -```typescript -const agent = createAgent({ - handler: async (ctx, input) => { - // Session data - expires after 24 hours - await ctx.kv.set('sessions', input.sessionId, sessionData, { - ttl: 86400 - }); - - // API cache - expires after 5 minutes - await ctx.kv.set('cache', cacheKey, apiResponse, { - ttl: 300 - }); - - // Rate limit counter - expires at end of hour - const secondsUntilHourEnd = 3600 - (Date.now() / 1000 % 3600); - await ctx.kv.set('rate-limits', input.userId, requestCount, { - ttl: Math.ceil(secondsUntilHourEnd) - }); - - // Feature flags - no TTL (persistent) - await ctx.kv.set('feature-flags', 'global', featureFlags); - - return { success: true }; - } -}); -``` - -### Data Size Considerations - -Key-value storage is optimized for small to medium-sized values. For large files or documents, consider using Object Storage instead. - -## Monitoring Usage - -Track your key-value storage usage through the Cloud Console: - -1. Navigate to **Services > Key Value** -2. View storage size and record count for each instance -3. Click on an instance to browse stored keys and values -4. Monitor agent telemetry for KV operation performance - - - -For more complex data relationships or query needs, consider combining storage types or using external databases through your agent. - -## Storage Types Overview - -TODO: Add video for v1 storage overview - -## Next Steps - -- [Agent Communication](/Guides/agent-communication): Share state between agents -- [Sessions and Threads](/Guides/sessions-threads): Manage conversation state -- [API Reference](/SDK/api-reference): Complete storage API documentation diff --git a/content/v1/Guides/object-storage.mdx b/content/v1/Guides/object-storage.mdx deleted file mode 100644 index 84854844..00000000 --- a/content/v1/Guides/object-storage.mdx +++ /dev/null @@ -1,529 +0,0 @@ ---- -title: Object Storage -description: Durable file storage for documents, images, videos, and binary content ---- - -# Object Storage - -Object storage provides durable file storage for agents. Use it for documents, images, videos, and any binary content. - -## When to Use Object Storage - -Object storage is your solution for storing files, media, and large unstructured data that agents need to manage. Use it for documents, images, videos, backups, and any binary content. - -Choose the right storage for your use case: - -- **Object Storage**: Files, media, documents, backups -- **[Key-Value Storage](/Guides/key-value-storage)**: Fast lookups, session data, configuration -- **[Vector Storage](/Guides/vector-storage)**: Semantic search, embeddings, AI context - -**Note:** For temporary files that only need to exist during agent execution, use `ctx.state` instead of Object Storage. See [Sessions and Threads](/Guides/sessions-threads) for built-in state management. - -## Common Use Cases - -- **File Management**: Store user uploads, generated documents, and processed files -- **Media Storage**: Keep images, videos, audio files, and other media assets -- **Document Processing**: Store PDFs, spreadsheets, and documents for agent processing -- **Backup and Archive**: Maintain backups of agent-generated content or historical data -- **Static Asset Serving**: Host files that can be accessed via public URLs -- **Data Export**: Store generated reports, exports, and downloadable content - -## Creating Object Storage - -You can create object storage buckets either through the Cloud Console or programmatically in your agent code. - -### Via Cloud Console - -Navigate to **Services > Object Store** and click **Create Storage**. Choose a descriptive bucket name that reflects its purpose (e.g., `user-uploads`, `processed-documents`, `media-assets`). - - - -### Via SDK - -Buckets are automatically created when you first access them: - -```typescript -import { createAgent } from '@agentuity/runtime'; - -const agent = createAgent({ - handler: async (ctx, input) => { - // TODO: Verify file upload input handling pattern - const imageData = new Uint8Array(input.fileData); - - // Bucket 'user-uploads' is auto-created if it doesn't exist - await ctx.objectstore.put('user-uploads', 'profile-123.jpg', imageData, { - contentType: 'image/jpeg' - }); - - return { message: 'Image uploaded successfully' }; - } -}); -``` - -## Working with Object Storage - -The object storage API provides four core operations: `get`, `put`, `delete`, and `createPublicURL`. All operations are asynchronous and support various content types. - -### Storing Objects - -Store files with optional metadata and HTTP headers: - -```typescript -import { createAgent } from '@agentuity/runtime'; - -const agent = createAgent({ - handler: async (ctx, input) => { - // Store with content type - await ctx.objectstore.put('documents', 'report.pdf', pdfData, { - contentType: 'application/pdf' - }); - - // Store with full metadata - await ctx.objectstore.put('uploads', 'document.pdf', pdfData, { - contentType: 'application/pdf', - contentDisposition: 'attachment; filename="report.pdf"', - cacheControl: 'max-age=3600', - metadata: { - 'uploaded-by': input.userId, - 'processed': 'false' - } - }); - - // Store text file - const textData = new TextEncoder().encode(logContent); - await ctx.objectstore.put('logs', 'agent.log', textData, { - contentType: 'text/plain', - contentEncoding: 'utf-8' - }); - - return { success: true }; - } -}); -``` - -**Put Parameters:** -- `contentType` (optional): MIME type of the file (default: `application/octet-stream`) -- `contentDisposition` (optional): Controls how browsers handle the file (download vs display) -- `cacheControl` (optional): Browser caching behavior -- `contentEncoding` (optional): Content encoding (e.g., `gzip`, `utf-8`) -- `contentLanguage` (optional): Content language (e.g., `en-US`) -- `metadata` (optional): Custom metadata as key-value pairs - -**Data Types:** -The `put` method accepts: -- `Uint8Array` - Binary data -- `ArrayBuffer` - Raw binary buffer -- `ReadableStream` - Streaming data - -### Retrieving Objects - -Retrieve stored objects with type-safe null checking: - -```typescript -import { createAgent } from '@agentuity/runtime'; - -const agent = createAgent({ - handler: async (ctx, input) => { - // Get an object - const result = await ctx.objectstore.get('documents', 'report.pdf'); - - if (result.exists) { - // Access binary data (Uint8Array) - const pdfData = result.data; - const contentType = result.contentType; - - ctx.logger.info(`PDF size: ${pdfData.byteLength} bytes`); - ctx.logger.info(`Content type: ${contentType}`); - } else { - ctx.logger.info('Object not found'); - } - - // Convert to text if needed - const textResult = await c.objectstore.get('logs', 'agent.log'); - if (textResult.exists) { - const logContent = new TextDecoder().decode(textResult.data); - ctx.logger.info('Log content:', logContent); - } - - return { found: result.exists }; - } -}); -``` - -**Get Return Value:** -Returns a discriminated union for type-safe null checking: -- `{ exists: true, data: Uint8Array, contentType: string }` - Object found -- `{ exists: false }` - Object not found - -**Converting Data:** -- Binary data is returned as `Uint8Array` -- Use `TextDecoder` to convert to string: `new TextDecoder().decode(data)` -- Use `TextEncoder` to convert string to Uint8Array: `new TextEncoder().encode(text)` - -### Generating Public URLs - -Create time-limited public URLs for direct access to objects: - -```typescript -import { createAgent } from '@agentuity/runtime'; - -const agent = createAgent({ - handler: async (ctx, input) => { - // Create a 1-hour public URL - const publicUrl = await ctx.objectstore.createPublicURL( - 'documents', - 'report.pdf', - { expiresDuration: 3600000 } // 1 hour in milliseconds - ); - - ctx.logger.info(`Download link: ${publicUrl}`); - - // Create with default expiration (1 hour) - const imageUrl = await ctx.objectstore.createPublicURL('images', 'photo.jpg'); - - // Create short-lived URL (minimum 1 minute) - const tempUrl = await ctx.objectstore.createPublicURL( - 'temp-files', - 'preview.png', - { expiresDuration: 60000 } - ); - - return { publicUrl, imageUrl, tempUrl }; - } -}); -``` - -**CreatePublicURL Parameters:** -- `expiresDuration` (optional): Duration in milliseconds (default: 1 hour, minimum: 1 minute) - -**Return Value:** -- Returns a string containing the signed public URL -- Throws an error if the object doesn't exist - -### Deleting Objects - -Remove objects when they're no longer needed: - -```typescript -import { createAgent } from '@agentuity/runtime'; - -const agent = createAgent({ - handler: async (ctx, input) => { - // Delete an object - const wasDeleted = await c.objectstore.delete('temp-files', 'processing.tmp'); - - if (wasDeleted) { - ctx.logger.info('Temporary file cleaned up'); - } else { - ctx.logger.info('File was already deleted'); - } - - return { deleted: wasDeleted }; - } -}); -``` - -**Delete Return Value:** -- Returns `true` if the object was deleted -- Returns `false` if the object didn't exist - -## Best Practices - -### Bucket Organization - -Structure your buckets by purpose and access patterns: -- `user-uploads`: User-submitted content -- `processed-output`: Agent-generated results -- `public-assets`: Files meant for public access -- `temp-storage`: Short-lived processing files - -### Key Naming Conventions - -Use hierarchical paths for better organization: -- `users/{userId}/profile.jpg` -- `documents/{year}/{month}/report-{id}.pdf` -- `exports/{timestamp}/data.csv` - -```typescript -const agent = createAgent({ - handler: async (ctx, input) => { - // Organize by user - const key = `users/${input.userId}/profile.jpg`; - await ctx.objectstore.put('uploads', key, imageData, { - contentType: 'image/jpeg' - }); - - // Organize by date - const date = new Date(); - const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, '0'); - const documentKey = `documents/${year}/${month}/report-${input.reportId}.pdf`; - - await ctx.objectstore.put('reports', documentKey, pdfData, { - contentType: 'application/pdf' - }); - - return { success: true }; - } -}); -``` - -### Content Type Management - -Always set appropriate content types for better browser handling: - -```typescript -import { createAgent } from '@agentuity/runtime'; - -const contentTypes: Record = { - '.jpg': 'image/jpeg', - '.jpeg': 'image/jpeg', - '.png': 'image/png', - '.gif': 'image/gif', - '.pdf': 'application/pdf', - '.json': 'application/json', - '.csv': 'text/csv', - '.txt': 'text/plain', - '.html': 'text/html', - '.xml': 'application/xml' -}; - -const agent = createAgent({ - handler: async (ctx, input) => { - const filename = input.filename; - const extension = filename.substring(filename.lastIndexOf('.')); - const contentType = contentTypes[extension] || 'application/octet-stream'; - - await ctx.objectstore.put('uploads', filename, input.fileData, { - contentType - }); - - return { success: true, contentType }; - } -}); -``` - -### Public URL Security - -Use appropriate expiration times for public URLs: - -```typescript -const agent = createAgent({ - handler: async (ctx, input) => { - let expiresDuration: number; - - switch (input.accessType) { - case 'temporary': - // Temporary downloads: 5-15 minutes - expiresDuration = 15 * 60 * 1000; // 15 minutes - break; - case 'shared': - // Shared documents: 1-24 hours - expiresDuration = 24 * 60 * 60 * 1000; // 24 hours - break; - case 'quick': - // Quick preview: 1-5 minutes - expiresDuration = 5 * 60 * 1000; // 5 minutes - break; - default: - // Default: 1 hour - expiresDuration = 60 * 60 * 1000; - } - - const url = await c.objectstore.createPublicURL( - 'documents', - input.documentKey, - { expiresDuration } - ); - - return { url, expiresIn: expiresDuration }; - } -}); -``` - -**Important:** Never create permanent public URLs for sensitive data. Always use time-limited signed URLs. - -### Error Handling - -Handle storage operations gracefully: - -```typescript -import { createAgent } from '@agentuity/runtime'; - -const agent = createAgent({ - handler: async (ctx, input) => { - try { - // Attempt to store file - await ctx.objectstore.put('uploads', input.key, input.data, { - contentType: input.contentType - }); - - // Generate temporary URL for response - const url = await c.objectstore.createPublicURL( - 'uploads', - input.key, - { expiresDuration: 900000 } // 15 minutes - ); - - return { - success: true, - url - }; - } catch (error) { - ctx.logger.error('Storage error:', error); - - return { - success: false, - error: 'Failed to process file' - }; - } - } -}); -``` - -## File Processing Patterns - -### Upload Handler - -Create a robust file upload handler: - -```typescript -import { createAgent } from '@agentuity/runtime'; -import { z } from 'zod'; - -// TODO: Verify the correct pattern for handling file uploads in v1 -// This example assumes file data comes through the input parameter -const uploadAgent = createAgent({ - schema: { - input: z.object({ - filename: z.string(), - contentType: z.string(), - fileData: z.instanceof(Uint8Array) - }), - output: z.object({ - message: z.string(), - key: z.string(), - url: z.string() - }) - }, - handler: async (ctx, input) => { - // Generate unique key - const timestamp = Date.now(); - const key = `uploads/${timestamp}-${input.filename}`; - - // Store the uploaded file - await ctx.objectstore.put('user-files', key, input.fileData, { - contentType: input.contentType, - metadata: { - 'original-name': input.filename, - 'upload-time': new Date().toISOString() - } - }); - - // Generate access URL (1 hour expiration) - const url = await c.objectstore.createPublicURL( - 'user-files', - key, - { expiresDuration: 3600000 } - ); - - return { - message: 'File uploaded successfully', - key, - url - }; - } -}); -``` - -### Document Processing Pipeline - -Process and store documents with metadata tracking: - -```typescript -import { createAgent } from '@agentuity/runtime'; -import { z } from 'zod'; - -const documentProcessorAgent = createAgent({ - schema: { - input: z.object({ - documentId: z.string(), - rawDocument: z.instanceof(Uint8Array) - }) - }, - handler: async (ctx, input) => { - // Store raw document - const rawKey = `documents/raw/${input.documentId}.pdf`; - await ctx.objectstore.put('documents', rawKey, input.rawDocument, { - contentType: 'application/pdf', - metadata: { - 'status': 'raw', - 'document-id': input.documentId, - 'uploaded-at': new Date().toISOString() - } - }); - - // Process document (example: convert to text) - const processedText = await processDocument(input.rawDocument); - const textData = new TextEncoder().encode(processedText); - - // Store processed version - const processedKey = `documents/processed/${input.documentId}.txt`; - await ctx.objectstore.put('documents', processedKey, textData, { - contentType: 'text/plain', - metadata: { - 'status': 'processed', - 'document-id': input.documentId, - 'processed-at': new Date().toISOString() - } - }); - - // Generate download URLs - const rawUrl = await c.objectstore.createPublicURL( - 'documents', - rawKey, - { expiresDuration: 3600000 } - ); - - const processedUrl = await c.objectstore.createPublicURL( - 'documents', - processedKey, - { expiresDuration: 3600000 } - ); - - return { - documentId: input.documentId, - rawUrl, - processedUrl - }; - } -}); - -// Helper function (example) -async function processDocument(pdfData: Uint8Array): Promise { - // Implement document processing logic - return 'Extracted text from document'; -} -``` - -## Monitoring Usage - -Track your object storage usage through the Cloud Console: - -1. Navigate to **Services > Object Store** -2. View storage size and object count for each bucket -3. Monitor provider information and creation dates -4. Track storage operations through agent telemetry - -For structured data with complex queries, consider using object storage to store data exports while maintaining indexes in key-value or vector storage. - -## Storage Types Overview - -TODO: Add video for v1 storage overview - -## Next Steps - -- [Key-Value Storage](/Guides/key-value-storage): Fast lookups for simple data -- [Vector Storage](/Guides/vector-storage): Semantic search and embeddings -- [Sessions and Threads](/Guides/sessions-threads): Manage conversation state -- [API Reference](/SDK/api-reference): Complete storage API documentation diff --git a/content/v1/Guides/vector-storage.mdx b/content/v1/Guides/vector-storage.mdx deleted file mode 100644 index 73e44742..00000000 --- a/content/v1/Guides/vector-storage.mdx +++ /dev/null @@ -1,954 +0,0 @@ ---- -title: Vector Storage -description: Semantic search and retrieval for knowledge bases, RAG systems, and agent memory ---- - -# Vector Storage - -Vector storage enables semantic search for your agents, allowing them to find information by meaning rather than keywords. Use it for knowledge bases, RAG systems, and persistent agent memory. - -## When to Use Vector Storage - -Vector storage enables semantic search for your agents, allowing them to find information by meaning rather than keywords. Ideal for knowledge bases, RAG systems, and persistent agent memory. - -Choose the right storage for your use case: - -- **Vector Storage**: Semantic search, embeddings, similarity matching -- **[Key-Value Storage](/Guides/key-value-storage)**: Fast lookups, simple data, temporary state -- **[Object Storage](/Guides/object-storage)**: Large files, media, backups - -## Understanding Vector Storage - -Vector storage works by converting text into high-dimensional numerical representations (embeddings) that capture semantic meaning. When you search, the system finds documents with similar meanings rather than just keyword matches. - -**Key use cases:** -- Knowledge bases and documentation search -- Long-term memory across agent sessions -- RAG systems combining retrieval with AI generation -- Semantic similarity search - -## Managing Vector Instances - -### Viewing Vector Storage in the Cloud Console - -Navigate to **Services > Vector** in the Agentuity Cloud Console to view all your vector storage instances. The interface shows: - -- **Database Name**: The identifier for your vector storage -- **Projects**: Which projects are using this storage -- **Agents**: Which agents have access -- **Size**: Storage utilization - -You can filter instances by name using the search box and create new vector storage instances with the **Create Storage** button. - -TODO: Add screenshot of Vector Storage Overview - -### Creating Vector Storage - -You can create vector storage either through the Cloud Console or programmatically in your agent code. - -#### Via Cloud Console - -Navigate to **Services > Vector** and click **Create Storage**. Choose a descriptive name that reflects the storage purpose (e.g., `knowledge-base`, `agent-memory`, `product-catalog`). - -#### Via SDK - -Vector storage is created automatically when your agent first calls `ctx.vector.upsert()` with an instance name: - -```typescript -import { createAgent } from '@agentuity/runtime'; - -const agent = createAgent({ - handler: async (ctx, input) => { - // Storage 'knowledge-base' is auto-created if it doesn't exist - await ctx.vector.upsert('knowledge-base', { - key: 'doc-1', - document: 'Agentuity is an agent-native cloud platform', - metadata: { category: 'platform' } - }); - - return { success: true }; - } -}); -``` - -## Vector Storage API - -All vector operations are accessed through `ctx.vector` in your agent handler. The API provides type-safe methods for upserting, searching, retrieving, and deleting vectors. - -### Upserting Documents - -The `upsert` operation inserts new documents or updates existing ones. You can provide either text (which gets automatically converted to embeddings) or pre-computed embeddings. - -**Key Requirement:** -- All documents require a `key` field as a unique identifier - -**Idempotent Behavior:** -The upsert operation is idempotent - upserting with an existing key updates the existing vector rather than creating a duplicate. The same internal vector ID is reused, ensuring your vector storage remains clean and efficient. - -```typescript -import { createAgent } from '@agentuity/runtime'; - -const agent = createAgent({ - handler: async (ctx, input) => { - // Upsert documents with text (automatic embedding) - const results = await ctx.vector.upsert( - 'knowledge-base', - { - key: 'doc-1', - document: 'Agentuity is an agent-native cloud platform', - metadata: { category: 'platform', source: 'docs' } - }, - { - key: 'doc-2', - document: 'Vector storage enables semantic search capabilities', - metadata: { category: 'features', source: 'docs' } - } - ); - - // Returns array of {key, id} mappings - // results = [ - // { key: 'doc-1', id: 'internal-id-123' }, - // { key: 'doc-2', id: 'internal-id-456' } - // ] - - return { insertedCount: results.length }; - } -}); -``` - -**Upsert with Pre-computed Embeddings:** - -```typescript -const agent = createAgent({ - handler: async (ctx, input) => { - // Upsert with pre-computed embeddings - const embeddingResults = await ctx.vector.upsert( - 'custom-embeddings', - { - key: 'embedding-1', - embeddings: [0.1, 0.2, 0.3, 0.4], - metadata: { id: 'doc-1', type: 'custom' } - } - ); - - return { success: true }; - } -}); -``` - -**Return Value:** -- Returns `VectorUpsertResult[]` - array of `{ key: string, id: string }` objects -- The `key` matches your provided key -- The `id` is the internal vector ID assigned by the service - -### Searching Vector Storage - -Search operations find semantically similar documents based on a text query. You can control the number of results, similarity threshold, and filter by metadata. - -```typescript -import { createAgent } from '@agentuity/runtime'; - -const agent = createAgent({ - handler: async (ctx, input) => { - // Basic semantic search - const results = await ctx.vector.search('knowledge-base', { - query: 'What is an agent platform?', - limit: 5, - similarity: 0.7, - metadata: { category: 'platform' } - }); - - // Process results - const formattedResults = results.map(result => ({ - content: result.metadata?.text, - source: result.metadata?.source, - similarity: result.similarity - })); - - return { results: formattedResults }; - } -}); -``` - -**Search Parameters:** -- `query` (required): Text query to search for -- `limit` (optional): Maximum number of results to return -- `similarity` (optional): Minimum similarity threshold (0.0-1.0) -- `metadata` (optional): Filter results by metadata key-value pairs - -**Search Results:** -Each result includes: -- `id`: Internal vector ID -- `key`: Your unique key -- `similarity`: Score from 0.0-1.0 (1.0 = perfect match) -- `metadata`: Your stored metadata object - -**Type Safety with Generics:** - -```typescript -interface DocumentMetadata { - title: string; - category: string; - source: string; - text: string; -} - -const agent = createAgent({ - handler: async (ctx, input) => { - // Specify metadata type for type-safe access - const results = await ctx.vector.search('knowledge-base', { - query: input.question, - limit: 3, - similarity: 0.7 - }); - - // TypeScript knows the shape of metadata - const titles = results.map(r => r.metadata?.title); - const categories = results.map(r => r.metadata?.category); - - return { titles, categories }; - } -}); -``` - -### Retrieving Vectors by Key - -The `get` method retrieves a specific vector directly using its key, without performing a similarity search. Returns a discriminated union for type-safe null checking. - -```typescript -import { createAgent } from '@agentuity/runtime'; - -const agent = createAgent({ - handler: async (ctx, input) => { - // Direct lookup by key - const result = await ctx.vector.get('knowledge-base', 'doc-1'); - - if (result.exists) { - // TypeScript knows result.data exists here - console.log(`Found: ${result.data.id}`); - console.log(`Key: ${result.data.key}`); - console.log('Metadata:', result.data.metadata); - } else { - console.log('Vector not found'); - } - - return { found: result.exists }; - } -}); -``` - -**Type-Safe with Discriminated Union:** - -The `get` method returns a discriminated union that TypeScript can narrow based on the `exists` property: - -```typescript -interface UserPreferences { - theme: string; - language: string; -} - -const agent = createAgent({ - handler: async (ctx, input) => { - const result = await ctx.vector.get('user-prefs', input.userId); - - // Type-safe access - if (result.exists) { - // TypeScript knows result.data is available - const theme = result.data.metadata?.theme; - const language = result.data.metadata?.language; - return { theme, language }; - } - - // Handle not found - return { theme: 'default', language: 'en' }; - } -}); -``` - -**Common Use Cases:** - -```typescript -// 1. Check if a vector exists before updating -const existing = await ctx.vector.get('products', 'product-123'); -if (existing.exists) { - // Update with merged metadata - await ctx.vector.upsert('products', { - key: 'product-123', - document: 'Updated product description', - metadata: { - ...(existing.data.metadata ?? {}), - lastUpdated: Date.now() - } - }); -} - -// 2. Retrieve full metadata after search -const searchResults = await ctx.vector.search('products', { - query: 'office chair', - limit: 5 -}); - -// Get complete details for the top result -if (searchResults[0]) { - const fullDetails = await ctx.vector.get('products', searchResults[0].key); - if (fullDetails.exists) { - console.log('Full metadata:', fullDetails.data.metadata); - } -} -``` - -**When to use `get` vs `search`:** -- Use `get` when you know the exact key (like a database primary key lookup) -- Use `search` when finding vectors by semantic similarity -- `get` is faster for single lookups since it doesn't compute similarities -- `get` returns `{ exists: false }` if the key is not found - -### Batch Retrieval with getMany() - -The `getMany` method retrieves multiple vectors by their keys in a single operation. Returns a `Map` for efficient lookups. - -```typescript -import { createAgent } from '@agentuity/runtime'; - -const agent = createAgent({ - handler: async (ctx, input) => { - // Retrieve multiple documents at once - const keys = ['doc-1', 'doc-2', 'doc-3']; - const resultMap = await ctx.vector.getMany('knowledge-base', ...keys); - - // resultMap is a Map> - const documents = []; - for (const [key, result] of resultMap) { - documents.push({ - key, - content: result.document, - metadata: result.metadata - }); - } - - return { - requestedCount: keys.length, - foundCount: resultMap.size, - documents - }; - } -}); -``` - -**Use Cases:** - -```typescript -// 1. Fetch user's saved documents -const agent = createAgent({ - handler: async (ctx, input) => { - const savedKeys = input.userSavedDocIds; // ['faq-1', 'guide-5', 'tutorial-3'] - const docs = await ctx.vector.getMany('docs', ...savedKeys); - - return { - savedDocs: Array.from(docs.values()).map(d => ({ - title: d.metadata?.title, - summary: d.metadata?.summary - })) - }; - } -}); - -// 2. Hydrate search results with full content -const agent = createAgent({ - handler: async (ctx, input) => { - // First, search for relevant documents - const searchResults = await ctx.vector.search('kb', { - query: input.question, - limit: 10 - }); - - // Then batch-fetch full details for top 3 - const topKeys = searchResults.slice(0, 3).map(r => r.key); - const fullDocs = await ctx.vector.getMany('kb', ...topKeys); - - return { - results: Array.from(fullDocs.values()).map(doc => ({ - content: doc.document, - metadata: doc.metadata, - similarity: searchResults.find(r => r.key === doc.key)?.similarity - })) - }; - } -}); -``` - -**Key Points:** -- Returns `Map>` for efficient key-based lookups -- Only includes vectors that were found (missing keys are absent from the Map) -- More efficient than calling `get()` multiple times -- Results include full document content and embeddings - -### Checking Storage Existence - -The `exists` method checks if a vector storage instance exists without retrieving any data. - -```typescript -import { createAgent } from '@agentuity/runtime'; - -const agent = createAgent({ - handler: async (ctx, input) => { - // Check before expensive operations - const hasKnowledgeBase = await ctx.vector.exists('knowledge-base'); - - if (!hasKnowledgeBase) { - return { - error: 'Knowledge base not initialized', - suggestion: 'Please upload documents first' - }; - } - - // Proceed with search - const results = await ctx.vector.search('knowledge-base', { - query: input.question - }); - - return { results }; - } -}); -``` - -**Common Use Cases:** - -```typescript -// 1. Conditional initialization -const agent = createAgent({ - handler: async (ctx, input) => { - const storageExists = await ctx.vector.exists('user-memory'); - - if (!storageExists) { - // Initialize with default documents - await ctx.vector.upsert('user-memory', { - key: 'welcome', - document: 'Welcome to the system', - metadata: { type: 'system' } - }); - } - - return { initialized: true }; - } -}); - -// 2. Feature availability check -const agent = createAgent({ - handler: async (ctx, input) => { - const features = { - search: await ctx.vector.exists('search-index'), - recommendations: await ctx.vector.exists('recommendations'), - history: await ctx.vector.exists('user-history') - }; - - return { availableFeatures: features }; - } -}); -``` - -**Performance Note:** -- Lightweight operation for checking availability -- Useful before expensive search operations -- Helps provide better error messages to users - -### Deleting Vectors - -Remove specific vectors from storage using their keys. - -```typescript -import { createAgent } from '@agentuity/runtime'; - -const agent = createAgent({ - handler: async (ctx, input) => { - // Delete single vector - const deletedCount = await ctx.vector.delete('knowledge-base', 'doc-1'); - - // Delete multiple vectors - const bulkDeleteCount = await ctx.vector.delete( - 'knowledge-base', - 'doc-1', 'doc-2', 'doc-3' - ); - - return { deletedCount: bulkDeleteCount }; - } -}); -``` - -**Return Value:** -- Returns the count of deleted vectors -- Returns `0` if no keys were provided or if keys don't exist - -**Use Cases:** - -```typescript -// 1. Remove outdated documents -const agent = createAgent({ - handler: async (ctx, input) => { - const outdatedKeys = ['old-doc-1', 'deprecated-faq']; - const count = await ctx.vector.delete('docs', ...outdatedKeys); - - ctx.logger.info(`Removed ${count} outdated documents`); - return { removed: count }; - } -}); - -// 2. User data deletion -const agent = createAgent({ - handler: async (ctx, input) => { - // Find all user's vectors - const userVectors = await ctx.vector.search('user-data', { - metadata: { userId: input.userId }, - limit: 1000 - }); - - // Delete all user's data - const keys = userVectors.map(v => v.key); - const deletedCount = await ctx.vector.delete('user-data', ...keys); - - return { - message: `Deleted ${deletedCount} user records`, - userId: input.userId - }; - } -}); -``` - -## Practical Examples - -### Building a Simple RAG System - -This example demonstrates a complete Retrieval-Augmented Generation (RAG) pattern - searching for relevant context and using it to generate informed responses. - -```typescript -import { createAgent } from '@agentuity/runtime'; -import { z } from 'zod'; -import { generateText } from 'ai'; -import { openai } from '@ai-sdk/openai'; - -const ragAgent = createAgent({ - schema: { - input: z.object({ question: z.string() }), - output: z.object({ - answer: z.string(), - sources: z.array(z.object({ - id: z.string(), - key: z.string(), - title: z.string().optional(), - similarity: z.number() - })) - }) - }, - handler: async (ctx, input) => { - try { - // 1. Search for relevant context (top 5 results) - const searchResults = await ctx.vector.search('knowledge-base', { - query: input.question, - limit: 5, - similarity: 0.7 - }); - - // 2. Handle no results gracefully - if (searchResults.length === 0) { - return { - answer: "I couldn't find relevant information to answer your question.", - sources: [] - }; - } - - // 3. Assemble context from search results - const contextTexts = searchResults.map(result => - result.metadata?.content ?? result.metadata?.text ?? '' - ); - const assembledContext = contextTexts.join('\n\n'); - - // 4. Generate response using context - const { text } = await generateText({ - model: openai('gpt-5-mini'), - prompt: `Answer the question based on the following context: - -Context: ${assembledContext} - -Question: ${input.question} - -Answer:` - }); - - // 5. Return answer with sources - return { - answer: text, - sources: searchResults.map(r => ({ - id: r.id, - key: r.key, - title: r.metadata?.title, - similarity: r.similarity - })) - }; - - } catch (error) { - ctx.logger.error('RAG query failed:', error); - throw error; - } - } -}); -``` - -**Key Points:** -- **Semantic search** finds relevant documents based on meaning, not keywords -- **Similarity threshold** of 0.7 balances relevance with recall -- **Context assembly** combines multiple sources for comprehensive answers -- **Error handling** ensures graceful failures with helpful messages -- **Source attribution** provides transparency about where information came from - - -For production RAG systems, add evaluations to check answer quality, hallucination detection, and faithfulness to sources. See the [Evaluations guide](/Guides/evaluations) for comprehensive RAG quality patterns including: -- **Hallucination Detection** - Verify answers are grounded in retrieved documents -- **RAG Quality Metrics** - Measure contextual relevancy, answer relevancy, and faithfulness -- **Using ctx.state** - Store retrieved documents for eval access - -These evaluation patterns help ensure your RAG system produces high-quality, trustworthy responses. - - -### Semantic Search with Metadata Filtering - -This example shows how to combine semantic similarity with metadata filters for precise results - like finding products that match both meaning and business criteria. - -```typescript -import { createAgent } from '@agentuity/runtime'; -import { z } from 'zod'; - -const productSearchAgent = createAgent({ - schema: { - input: z.object({ - query: z.string(), - maxPrice: z.number().optional(), - category: z.string().optional(), - inStock: z.boolean().optional() - }), - output: z.object({ - query: z.string(), - filters: z.record(z.unknown()), - resultCount: z.number(), - products: z.array(z.record(z.unknown())) - }) - }, - handler: async (ctx, input) => { - try { - // Build metadata filters based on criteria - const metadataFilters: Record = {}; - if (input.category) metadataFilters.category = input.category; - if (input.inStock !== undefined) metadataFilters.inStock = input.inStock; - - // Search with semantic similarity + metadata filters - const searchResults = await ctx.vector.search('products', { - query: input.query, - limit: 10, - similarity: 0.65, // Lower threshold for broader results - metadata: metadataFilters - }); - - // Post-process: Apply price filter and sort by relevance - const filteredResults = searchResults - .filter(result => !input.maxPrice || (result.metadata?.price as number) <= input.maxPrice) - .map(result => ({ - ...result.metadata, - similarity: result.similarity - })) - .sort((a, b) => (b.similarity as number) - (a.similarity as number)); - - return { - query: input.query, - filters: { - maxPrice: input.maxPrice, - category: input.category, - inStock: input.inStock - }, - resultCount: filteredResults.length, - products: filteredResults.slice(0, 5) // Top 5 results - }; - - } catch (error) { - ctx.logger.error('Product search failed:', error); - return { - query: input.query, - filters: {}, - resultCount: 0, - products: [] - }; - } - } -}); -``` - -**Key Techniques:** -- **Metadata filters** are applied at the vector search level for efficiency -- **Post-processing** handles filters that can't be done at search time (like price ranges) -- **Lower similarity threshold** (0.65) catches more potential matches when using strict filters -- **Type-safe schemas** ensure input validation and output consistency - -## Common Pitfalls & Solutions - -### Empty Search Results - -**Problem**: Your search returns empty results even though relevant data exists. - -**Solutions:** -- **Lower the similarity threshold**: Start at 0.5 and increase gradually -- **Check your metadata filters**: They use exact matching, not fuzzy matching -- **Verify document format**: Ensure documents were upserted with text content - -```typescript -const agent = createAgent({ - handler: async (ctx, input) => { - // Adaptive threshold example - let results = await ctx.vector.search('kb', { - query: input.question, - similarity: 0.8 - }); - - if (results.length === 0) { - // Try again with lower threshold - results = await ctx.vector.search('kb', { - query: input.question, - similarity: 0.5 - }); - } - - return { results }; - } -}); -``` - -### Duplicate Documents - -**Problem**: Same content appears multiple times in search results. - -**Solution**: Vector upsert is idempotent when using the same key: -- Always use consistent `key` values for your documents -- Upserting with an existing key updates the vector rather than creating a duplicate -- The same internal vector ID is reused, keeping your storage clean - -```typescript -const agent = createAgent({ - handler: async (ctx, input) => { - // This will update existing doc-1, not create a duplicate - await ctx.vector.upsert('docs', { - key: 'doc-1', - document: 'Updated content', - metadata: { version: 2 } - }); - - return { success: true }; - } -}); -``` - -### Performance Issues - -**Problem**: Vector operations take too long. - -**Solutions:** -- **Batch operations**: Upsert 100-500 documents at once, not one by one -- **Limit search results**: Use `limit: 10` instead of retrieving all matches -- **Optimize metadata**: Keep metadata objects small and focused -- **Use getMany()**: Batch retrieval instead of multiple `get()` calls - -```typescript -const agent = createAgent({ - handler: async (ctx, input) => { - // Good: Batch upsert - const documents = input.docs.map(doc => ({ - key: doc.id, - document: doc.content, - metadata: { title: doc.title } - })); - await ctx.vector.upsert('kb', ...documents); - - // Good: Limited search - const results = await ctx.vector.search('kb', { - query: input.query, - limit: 10 - }); - - return { count: documents.length, results }; - } -}); -``` - -### Irrelevant Search Results - -**Problem**: Search returns irrelevant or unexpected documents. - -**Solutions:** -- **Check similarity scores**: Results with similarity < 0.7 may be poor matches -- **Review metadata filters**: Remember they're AND conditions, not OR -- **Verify embeddings**: Ensure consistent text preprocessing before upserting - -```typescript -const agent = createAgent({ - handler: async (ctx, input) => { - const results = await ctx.vector.search('kb', { - query: input.question, - limit: 10, - similarity: 0.7 // Minimum quality threshold - }); - - // Filter by additional quality checks - const qualityResults = results.filter(r => { - // Only include high-quality matches - return r.similarity >= 0.75 && r.metadata?.verified === true; - }); - - return { results: qualityResults }; - } -}); -``` - -## Best Practices - -### Document Structure - -- **Include context in documents**: Store enough context so documents are meaningful when retrieved -- **Use descriptive metadata**: Include relevant metadata for filtering and identification -- **Consistent formatting**: Use consistent document formatting for better embeddings - -```typescript -// Good: Rich, contextual documents -await ctx.vector.upsert('docs', { - key: 'getting-started', - document: 'Getting Started with Agentuity: This guide walks you through creating your first agent, including setup, configuration, and deployment.', - metadata: { - title: 'Getting Started Guide', - category: 'documentation', - tags: ['beginner', 'setup', 'tutorial'], - lastUpdated: Date.now() - } -}); - -// Avoid: Minimal context -await ctx.vector.upsert('docs', { - key: 'doc1', - document: 'Setup guide', - metadata: { type: 'doc' } -}); -``` - -### Search Optimization - -- **Adjust similarity thresholds**: Start with 0.7 and adjust based on result quality -- **Use metadata filtering**: Combine semantic search with metadata filters for precise results -- **Limit result sets**: Use appropriate limits to balance performance and relevance - -```typescript -const agent = createAgent({ - handler: async (ctx, input) => { - // Optimized search with filters - const results = await ctx.vector.search('products', { - query: input.searchTerm, - limit: 20, // Reasonable limit - similarity: 0.7, // Quality threshold - metadata: { - active: true, // Only active products - category: input.category - } - }); - - return { results }; - } -}); -``` - -### Performance Considerations - -- **Batch upsert operations**: Use bulk upsert instead of individual calls -- **Monitor storage usage**: Track vector storage size in the Cloud Console -- **Consider document chunking**: Break large documents into smaller, focused chunks -- **Use type parameters**: Leverage TypeScript generics for type-safe metadata access - -```typescript -// Good: Batch operations -const agent = createAgent({ - handler: async (ctx, input) => { - // Process documents in batches - const batchSize = 100; - const documents = input.documents; - - for (let i = 0; i < documents.length; i += batchSize) { - const batch = documents.slice(i, i + batchSize); - const vectors = batch.map(doc => ({ - key: doc.id, - document: doc.content, - metadata: { title: doc.title, category: doc.category } - })); - - await ctx.vector.upsert('kb', ...vectors); - } - - return { processed: documents.length }; - } -}); -``` - -### Type Safety - -Use TypeScript interfaces for consistent, type-safe metadata: - -```typescript -interface DocumentMetadata { - title: string; - category: 'guide' | 'api' | 'tutorial'; - tags: string[]; - author: string; - lastUpdated: number; -} - -const agent = createAgent({ - handler: async (ctx, input) => { - // Type-safe upsert - await ctx.vector.upsert('docs', { - key: 'example', - document: 'Content here', - metadata: { - title: 'Example', - category: 'guide', - tags: ['intro'], - author: 'system', - lastUpdated: Date.now() - } - }); - - // Type-safe search - const results = await ctx.vector.search('docs', { - query: input.question, - metadata: { category: 'guide' } - }); - - // TypeScript knows the metadata structure - const titles = results.map(r => r.metadata?.title); - - return { titles }; - } -}); -``` - -## Integration with Agent Memory - -Vector storage serves as long-term memory for agents, enabling them to: - -- Remember past conversations and context across sessions -- Access organizational knowledge bases -- Retrieve relevant examples for few-shot learning -- Build and maintain agent-specific knowledge repositories - -For more information on memory patterns, see the [Key-Value Storage guide](/Guides/key-value-storage) for short-term memory or explore [Agent Communication](/Guides/agent-communication) for sharing knowledge between agents. - -## Storage Types Overview - -TODO: Add video for v1 storage overview - -## Next Steps - -- [Evaluations](/Guides/evaluations): Add quality checks for RAG systems (hallucination detection, faithfulness, relevancy) -- [Key-Value Storage](/Guides/key-value-storage): Fast lookups for simple data -- [Sessions and Threads](/Guides/sessions-threads): Manage conversation state -- [API Reference](/SDK/api-reference): Complete vector storage API documentation From c790d4f77e28d38865f6061f02f0a319822fc3d2 Mon Sep 17 00:00:00 2001 From: Parteek Singh Date: Tue, 2 Dec 2025 10:16:34 -0800 Subject: [PATCH 17/63] Add Reference (CLI) pages --- content/v1/Get-Started/project-structure.mdx | 4 + content/v1/Get-Started/what-is-agentuity.mdx | 2 +- content/v1/Guides/agent-logging.mdx | 416 ----------------- content/v1/Reference/CLI/ai-commands.mdx | 110 +++++ content/v1/Reference/CLI/data-management.mdx | 426 ++++++++++++++++++ content/v1/Reference/CLI/debugging.mdx | 290 ++++++++++++ content/v1/Reference/CLI/deployment.mdx | 395 ++++++++++++++++ content/v1/Reference/CLI/development.mdx | 214 +++++++++ content/v1/Reference/CLI/getting-started.mdx | 308 +++++++++++++ content/v1/Reference/CLI/meta.json | 11 + content/v1/Reference/Migration/meta.json | 6 + .../Migration}/migration-guide.mdx | 5 + .../v1/{ => Reference}/SDK/api-reference.mdx | 0 content/v1/Reference/SDK/meta.json | 6 + content/v1/Reference/meta.json | 8 + content/v1/meta.json | 3 +- 16 files changed, 1786 insertions(+), 418 deletions(-) delete mode 100644 content/v1/Guides/agent-logging.mdx create mode 100644 content/v1/Reference/CLI/ai-commands.mdx create mode 100644 content/v1/Reference/CLI/data-management.mdx create mode 100644 content/v1/Reference/CLI/debugging.mdx create mode 100644 content/v1/Reference/CLI/deployment.mdx create mode 100644 content/v1/Reference/CLI/development.mdx create mode 100644 content/v1/Reference/CLI/getting-started.mdx create mode 100644 content/v1/Reference/CLI/meta.json create mode 100644 content/v1/Reference/Migration/meta.json rename content/v1/{ => Reference/Migration}/migration-guide.mdx (98%) rename content/v1/{ => Reference}/SDK/api-reference.mdx (100%) create mode 100644 content/v1/Reference/SDK/meta.json create mode 100644 content/v1/Reference/meta.json diff --git a/content/v1/Get-Started/project-structure.mdx b/content/v1/Get-Started/project-structure.mdx index c20c44ae..ca5306a4 100644 --- a/content/v1/Get-Started/project-structure.mdx +++ b/content/v1/Get-Started/project-structure.mdx @@ -5,6 +5,10 @@ description: Understand how Agentuity projects are organized Agentuity projects follow a convention-based structure that enables automatic discovery and deployment. + +Creating a new agent is as simple as adding a folder to `src/agents/` with `agent.ts` and `route.ts`. No CLI commands, no manual registration — agents are auto-discovered and registered when you deploy. + + ## Directory Layout ``` diff --git a/content/v1/Get-Started/what-is-agentuity.mdx b/content/v1/Get-Started/what-is-agentuity.mdx index 41602084..1d354908 100644 --- a/content/v1/Get-Started/what-is-agentuity.mdx +++ b/content/v1/Get-Started/what-is-agentuity.mdx @@ -60,7 +60,7 @@ Infrastructure like cron schedules and email handlers are defined in code, not c --- -

We're building for a future where agents are the primary way to build and operate software, and where infrastructure is purpose-built for this new paradigm.

+

We're building for a future where agents are the primary way to build and operate software, and where infrastructure is purpose-built for this new paradigm.

## Next Steps diff --git a/content/v1/Guides/agent-logging.mdx b/content/v1/Guides/agent-logging.mdx deleted file mode 100644 index 6eaefe34..00000000 --- a/content/v1/Guides/agent-logging.mdx +++ /dev/null @@ -1,416 +0,0 @@ ---- -title: Agent Logging -description: Structured, real-time insights into agent execution for debugging and monitoring ---- - -# Agent Logging - -Agent logging provides structured, real-time insights into your agent's execution. Effective logging helps you debug issues, monitor behavior, and understand agent decision-making. - -## Logging Interface - -The Agentuity platform provides persistent, searchable logs with real-time streaming for all deployed agents. - -TODO: Update screenshot for v1 Logs Overview - -### Log Overview - -The Logs dashboard displays: - -- **Timestamps**: Precise timing for each log entry -- **Severity levels**: TRACE, DEBUG, INFO, WARN, ERROR, FATAL for categorization -- **Source identification**: Which component generated the log -- **Detailed messages**: Context about agent actions - -### Search and Filtering - -**AI-Powered Search:** -Use natural language queries to find log entries. Click the purple sparkle icon and enter your search: - -TODO: Update screenshot for v1 AI-Powered Log Search - -**Filtering Options:** -- **Severity**: Filter by log level (TRACE, DEBUG, INFO, WARN, ERROR, FATAL) -- **Project**: Scope logs to specific projects -- **Agent**: View logs from specific agents -- **Session ID**: Filter logs for a particular session -- **Deployment ID**: View logs from specific deployments -- **Time Range**: Focus on specific time periods - -### Detailed Log Analysis - -Each log entry provides comprehensive context and can be expanded for full details: - -TODO: Update screenshot for v1 Detailed Log Entry View - -## Logging Best Practices - -### 1. Use Appropriate Log Levels - -```typescript -import { createAgent } from '@agentuity/runtime'; - -const agent = createAgent({ - handler: async (ctx, input) => { - // TRACE: Very detailed step-by-step execution (hidden in production) - ctx.logger.trace('RAG retrieval step 1: Embedding query'); - ctx.logger.trace('RAG retrieval step 2: Searching vector store', { query: input.question }); - ctx.logger.trace('RAG retrieval step 3: Ranking results', { count: results.length }); - - // DEBUG: Detailed information for debugging - ctx.logger.debug('Cache lookup', { key: cacheKey, hit: true }); - - // INFO: Normal flow and state changes - ctx.logger.info('Processing user request', { userId: input.userId, action: 'search' }); - - // WARN: Potential issues that don't stop execution - ctx.logger.warn('Rate limit approaching', { remaining: 5, limit: 100 }); - - // ERROR: Failures requiring attention but execution continues - ctx.logger.error('Failed to fetch data', { error: error.message, retrying: true }); - - // FATAL: Critical errors that terminate execution - // ctx.logger.fatal('Application cannot continue: Critical initialization failure'); - - return { result: 'success' }; - } -}); -``` - -**When to use TRACE:** -- Multi-step algorithms showing each calculation -- RAG systems showing retrieval, ranking, generation steps -- Complex data transformations showing each stage -- Multi-agent workflows showing each agent call - -**When to use DEBUG:** -- Cache hits/misses -- Variable values during execution -- Conditional branch decisions -- Database query details - -**When to use INFO:** -- Request processing started/completed -- State changes -- Successful operations -- Normal workflow milestones - -**When to use WARN:** -- Approaching resource limits -- Deprecated feature usage -- Fallback behavior triggered -- Recoverable errors - -**When to use ERROR:** -- Failed operations with retry -- External service failures -- Validation errors -- Caught exceptions - -**When to use FATAL:** -- Critical errors that require immediate termination -- Unrecoverable initialization failures -- System-level failures that prevent operation -- Note: FATAL terminates the process immediately - -### 2. Use Structured Data - -Use structured data to provide context and make logs easier to parse and analyze: - -```typescript -import { createAgent } from '@agentuity/runtime'; - -const agent = createAgent({ - handler: async (ctx, input) => { - // Good: Use objects for structured context - ctx.logger.info('API call completed', { - endpoint: '/api/users', - method: 'GET', - duration: 234, - status: 200, - contentType: 'application/json' - }); - - // Extract variables for dynamic values - const orderId = input.order.id; - const itemCount = input.order.items.length; - - ctx.logger.info('Order processed', { - orderId, - itemCount, - total: input.order.total - }); - - return { success: true }; - } -}); -``` - -### 3. Include Relevant Context - -Log enough information to understand what happened without re-running: - -```typescript -import { createAgent } from '@agentuity/runtime'; - -const agent = createAgent({ - handler: async (ctx, input) => { - // Good - includes context - ctx.logger.info('Order processed', { - orderId: input.order.id, - customerId: input.customer.id, - total: input.order.total, - itemCount: input.order.items.length - }); - - // Less helpful - missing context - // ctx.logger.info('Order done'); - - return { success: true }; - } -}); -``` - -### 4. Include Agentuity-Specific Information - -Add Agentuity-specific information to help with debugging and monitoring: - -```typescript -import { createAgent } from '@agentuity/runtime'; - -const agent = createAgent({ - handler: async (ctx, input) => { - // Include available Agentuity context - ctx.logger.info('Agent processing request', { - agentName: c.agentName, - sessionId: c.sessionId - // Note: ctx.agent.id and req.trigger not available in v1 - }); - - // For errors, include additional context - try { - const result = await processData(input); - return result; - } catch (error) { - ctx.logger.error('Agent execution failed', { - agentName: c.agentName, - sessionId: c.sessionId, - error: error.message - }); - throw error; - } - } -}); -``` - -**Note:** v1 does not provide `c.agent.id` or `req.trigger` properties. Use `c.agentName` for agent identification and `c.sessionId` for request tracking. - -### 5. Log Decision Points - -Help future debugging by logging key decisions: - -```typescript -import { createAgent } from '@agentuity/runtime'; - -const agent = createAgent({ - handler: async (ctx, input) => { - const cacheKey = `user:${input.userId}:profile`; - const cached = await c.kv.get('cache', cacheKey); - - if (cached.exists) { - const cacheAge = Date.now() - cached.data.timestamp; - ctx.logger.info('Using cached response', { - cacheKey, - age: cacheAge - }); - return cached.data.value; - } else { - ctx.logger.info('Cache miss, fetching fresh data', { - cacheKey, - reason: 'not found' - }); - const freshData = await fetchUserProfile(input.userId); - await c.kv.set('cache', cacheKey, { value: freshData, timestamp: Date.now() }); - return freshData; - } - } -}); -``` - -### 6. Use Child Loggers for Different Tasks - -Create child loggers to organize logging for different parts of your workflow: - -```typescript -import { createAgent } from '@agentuity/runtime'; - -const agent = createAgent({ - handler: async (ctx, input) => { - // Create child loggers for different tasks - const orderLogger = c.logger.child({ component: 'order-processing' }); - const paymentLogger = c.logger.child({ component: 'payment' }); - const inventoryLogger = c.logger.child({ component: 'inventory' }); - - // Use specific loggers for their respective operations - orderLogger.info('Starting order validation'); - - const orderId = input.order.id; - const itemCount = input.order.items.length; - const paymentAmount = input.order.total; - - orderLogger.info('Order validated', { - orderId, - itemCount, - total: paymentAmount - }); - - paymentLogger.info('Processing payment', { - orderId, - amount: paymentAmount - }); - - inventoryLogger.info('Checking inventory', { - orderId, - items: itemCount - }); - - return { success: true, orderId }; - } -}); -``` - -**Benefits of child loggers:** -- Automatic context propagation (component name) -- Easier filtering and searching in logs -- Clear separation of concerns -- Consistent formatting across related operations - -## Common Logging Patterns - -### Pattern 1: RAG System with Detailed Tracing - -```typescript -import { createAgent } from '@agentuity/runtime'; -import { z } from 'zod'; - -const ragAgent = createAgent({ - schema: { - input: z.object({ question: z.string() }), - output: z.object({ answer: z.string(), sources: z.array(z.string()) }) - }, - handler: async (ctx, input) => { - ctx.logger.trace('RAG pipeline started'); - - // Step 1: Embedding - ctx.logger.trace('Generating query embedding'); - const embedding = await generateEmbedding(input.question); - ctx.logger.debug('Query embedding generated', { dimensions: embedding.length }); - - // Step 2: Retrieval - ctx.logger.trace('Searching vector store'); - const results = await c.vector.search('knowledge-base', { - query: input.question, - limit: 5, - similarity: 0.7 - }); - ctx.logger.info('Retrieved documents', { count: results.length }); - - // Step 3: Generation - ctx.logger.trace('Generating answer from context'); - const answer = await generateAnswer(input.question, results); - ctx.logger.info('Answer generated', { length: answer.length }); - - return { - answer, - sources: results.map(r => r.key) - }; - } -}); -``` - -### Pattern 2: Multi-Agent Workflow - -```typescript -import { createAgent } from '@agentuity/runtime'; - -const coordinatorAgent = createAgent({ - handler: async (ctx, input) => { - const workflowLogger = c.logger.child({ workflow: 'data-processing' }); - - workflowLogger.info('Workflow started', { inputSize: input.data.length }); - - // Step 1: Validation - workflowLogger.debug('Calling validation agent'); - const validated = await c.agent.validator.run(input); - workflowLogger.info('Validation complete', { valid: validated.isValid }); - - // Step 2: Processing - workflowLogger.debug('Calling processing agent'); - const processed = await c.agent.processor.run(validated); - workflowLogger.info('Processing complete', { recordsProcessed: processed.count }); - - // Step 3: Storage - workflowLogger.debug('Calling storage agent'); - const stored = await c.agent.storage.run(processed); - workflowLogger.info('Storage complete', { stored: stored.success }); - - workflowLogger.info('Workflow completed successfully'); - - return { - success: true, - processedCount: processed.count - }; - } -}); -``` - -### Pattern 3: Error Handling with Context - -```typescript -import { createAgent } from '@agentuity/runtime'; - -const agent = createAgent({ - handler: async (ctx, input) => { - const startTime = Date.now(); - - try { - ctx.logger.info('Processing request', { - agentName: c.agentName, - sessionId: c.sessionId, - userId: input.userId - }); - - const result = await performComplexOperation(input); - - const duration = Date.now() - startTime; - ctx.logger.info('Request completed successfully', { - duration, - resultSize: JSON.stringify(result).length - }); - - return result; - - } catch (error) { - const duration = Date.now() - startTime; - - ctx.logger.error('Request failed', { - agentName: c.agentName, - sessionId: c.sessionId, - userId: input.userId, - duration, - error: error.message, - stack: error.stack - }); - - // Re-throw or handle as appropriate - throw error; - } - } -}); -``` - -## Next Steps - -- [Agent Communication](/Guides/agent-communication): Share state between agents -- [Sessions and Threads](/Guides/sessions-threads): Manage conversation state -- [API Reference](/SDK/api-reference): Complete logging API documentation diff --git a/content/v1/Reference/CLI/ai-commands.mdx b/content/v1/Reference/CLI/ai-commands.mdx new file mode 100644 index 00000000..2de45930 --- /dev/null +++ b/content/v1/Reference/CLI/ai-commands.mdx @@ -0,0 +1,110 @@ +--- +title: AI Commands +description: CLI commands for AI agents, IDE integration, and schema inspection. +--- + +The CLI includes commands designed for AI coding agents, IDE integrations, and tooling that needs to understand the Agentuity CLI programmatically. + +## CLI Capabilities + +Show the CLI's capabilities in a structured format for AI consumption: + +```bash +agentuity ai capabilities show +``` + +This outputs a machine-readable description of what the CLI can do, useful for: +- AI coding agents that need to understand available commands +- IDE extensions building Agentuity integrations +- Documentation generators + +## Schema Inspection + +Output the CLI's command schema for programmatic consumption: + +```bash +# Show the full CLI schema +agentuity ai schema show + +# Generate schema in a specific format +agentuity ai schema generate +``` + +The schema includes: +- All available commands and subcommands +- Required and optional parameters +- Parameter types and validation rules +- Command descriptions and examples + +## Prompt Generation + +Generate context-aware prompts for LLMs working with Agentuity projects: + +```bash +# Generate prompt for building agents +agentuity ai prompt agent + +# Generate prompt for building APIs +agentuity ai prompt api + +# Generate general LLM prompt +agentuity ai prompt llm + +# Generate prompt for web/frontend development +agentuity ai prompt web +``` + +Each prompt type includes: +- Relevant SDK patterns and best practices +- Code examples for the target domain +- Common pitfalls to avoid +- Links to documentation + + +These prompts are useful for: +- Bootstrapping AI coding sessions with Agentuity context +- Training custom AI assistants on Agentuity patterns +- Generating project-specific documentation + + +## Integration Examples + +### IDE Extension + +Use the CLI to provide autocomplete, validation, and project configuration help: + +```typescript +// Get CLI capabilities for IDE integration +const { execSync } = require('child_process'); + +const capabilities = JSON.parse( + execSync('agentuity --json ai capabilities show').toString() +); + +// Use capabilities to build autocomplete, validation, etc. +``` + +```typescript +// Get agentuity.json schema for configuration validation +const schema = JSON.parse( + execSync('agentuity --json ai schema generate').toString() +); + +// Use schema to validate project configuration files +``` + +### AI Coding Agent + +```bash +# Generate context for an AI agent working on your project +agentuity ai prompt agent > /tmp/agentuity-context.md + +# Include in your AI agent's system prompt +cat /tmp/agentuity-context.md +``` + +## Next Steps + +- [Getting Started with the CLI](/v1/Reference/CLI/getting-started): Install and authenticate +- [Local Development](/v1/Reference/CLI/development): Run agents locally +- [Creating Agents](/v1/Build/Agents/creating-agents): Build your first agent diff --git a/content/v1/Reference/CLI/data-management.mdx b/content/v1/Reference/CLI/data-management.mdx new file mode 100644 index 00000000..0ea64a44 --- /dev/null +++ b/content/v1/Reference/CLI/data-management.mdx @@ -0,0 +1,426 @@ +--- +title: Managing Cloud Data +description: Inspect and manage storage, environment variables, secrets, and API keys from the CLI. +--- + +These CLI commands help you inspect and manage cloud data during development and debugging. For programmatic access in your agents, use the SDK context objects like `ctx.kv`, `ctx.vector`, and `ctx.object`. + +## Key-Value Storage + +Inspect and manage key-value data organized into namespaces. + +### Interactive REPL + +Start an interactive session for faster exploration: + +```bash +agentuity cloud kv repl +``` + +In the REPL, use commands like `set`, `get`, `delete`, `keys`, and `stats`: + +``` +> set cache user:123 '{"name":"Alice"}' +> get cache user:123 +> keys cache +> stats +``` + +### Get a Value + +```bash +agentuity cloud kv get production user:123 +agentuity cloud kv get cache session:abc +``` + + +All data management commands require the `cloud` prefix. See [Command Shortcuts](/v1/Reference/CLI/getting-started#command-shortcuts) for commands that have shortcuts. + + +### Set a Value + +```bash +# Store JSON data +agentuity cloud kv set production user:123 '{"name":"Alice","email":"alice@example.com"}' + +# Set with TTL (expires after 1 hour) +agentuity cloud kv set cache session:abc "session-data" --ttl 3600 +``` + +### Delete a Key + +```bash +agentuity cloud kv delete production user:123 +agentuity cloud kv rm cache session:abc # Using alias +``` + +### List Keys + +```bash +agentuity cloud kv keys production +agentuity cloud kv ls cache # Using alias +``` + +### Search Keys + +```bash +agentuity cloud kv search production user +agentuity cloud kv search cache session +``` + +### View Statistics + +```bash +# Stats for all namespaces +agentuity cloud kv stats + +# Stats for specific namespace +agentuity cloud kv stats production +``` + +### Namespace Management + +A namespace is a logical container that groups related keys together. For example, you might use `cache` for temporary data, `users` for user profiles, and `sessions` for session state. Namespaces help organize your data and prevent key collisions. + +```bash +# List all namespaces +agentuity cloud kv list-namespaces +agentuity cloud kv ns # Using alias + +# Create a namespace +agentuity cloud kv create-namespace staging + +# Delete a namespace and all its keys +agentuity cloud kv delete-namespace old-cache +``` + + +For programmatic access in agents, use `ctx.kv`: + +```typescript +await ctx.kv.set('cache', 'user:123', data); +const value = await ctx.kv.get('cache', 'user:123'); +``` + +See [Using Key-Value Storage](/v1/Build/Storage/key-value) for details. + + +## Object Storage + +Manage file uploads and binary objects in buckets. + +### Interactive REPL + +```bash +agentuity cloud obj repl +``` + +### Upload a File + +```bash +# Upload from file path +agentuity cloud obj put uploads images/logo.png @./logo.png + +# Store JSON directly +agentuity cloud obj put assets config.json '{"api":"https://api.example.com"}' + +# With custom content type +agentuity cloud obj put backups db.sql @~/backup.sql --content-type application/sql +``` + +### Download a File + +```bash +agentuity cloud obj get uploads images/logo.png +agentuity cloud obj get assets config.json +``` + +### Delete a File + +```bash +agentuity cloud obj delete uploads old-image.png +``` + +### Generate Public URL + +```bash +# Permanent URL +agentuity cloud obj url uploads images/logo.png + +# Temporary URL (expires in 1 hour) +agentuity cloud obj url uploads private-doc.pdf --expires 3600 + +# 5-minute presigned URL +agentuity cloud obj presigned backups db.sql --expires 300 +``` + +### List Buckets + +```bash +agentuity cloud obj list-buckets +``` + +### List Files in a Bucket + +```bash +agentuity cloud obj list-keys uploads +agentuity cloud obj ls assets # Using alias +``` + + +For programmatic access, use `ctx.objectstore`: + +```typescript +await ctx.objectstore.put('uploads', 'image.png', buffer); +const file = await ctx.objectstore.get('uploads', 'image.png'); +``` + +See [Object Storage](/v1/Build/Storage/object) for details. + + +## Vector Storage + +Search and inspect vector embeddings. + +### Search by Similarity + +```bash +# Basic search +agentuity cloud vector search products "comfortable office chair" + +# Limit results +agentuity cloud vector search docs "API documentation" --limit 5 + +# Set minimum similarity threshold +agentuity cloud vector search products "ergonomic" --similarity 0.8 + +# Filter by metadata +agentuity cloud vector search embeddings "neural networks" --metadata category=ai +``` + +### Get Vector by ID + +```bash +agentuity cloud vec get +``` + +### Delete a Vector + +```bash +agentuity cloud vec delete +``` + + +For programmatic access, use `ctx.vector`: + +```typescript +const results = await ctx.vector.search('products', { + query: 'comfortable chair', + limit: 10 +}); +``` + +See [Vector Storage](/v1/Build/Storage/vector) for details. + + +## Streams + +List and manage durable event streams. + +### List Streams + +```bash +agentuity cloud stream list +``` + +### Get Stream Details + +```bash +agentuity cloud stream get +``` + +### Delete a Stream + +```bash +agentuity cloud stream delete +``` + +## Environment Variables + +Manage non-sensitive configuration values. + +### List Variables + +```bash +agentuity cloud env list +``` + +### Get a Variable + +```bash +agentuity cloud env get NODE_ENV +``` + +### Set a Variable + +```bash +agentuity cloud env set NODE_ENV production +agentuity cloud env set API_URL https://api.example.com +``` + +### Delete a Variable + +```bash +agentuity cloud env delete OLD_CONFIG +``` + +### Push from Local File + +Upload variables from `.env.production` (or `.env`) to cloud: + +```bash +agentuity cloud env push +``` + +This filters out `AGENTUITY_` prefixed keys automatically. + +### Pull to Local File + +Download cloud variables to `.env.production`: + +```bash +# Merge with local (local values take priority) +agentuity cloud env pull + +# Overwrite local with cloud values +agentuity cloud env pull --force +``` + +### Import from File + +```bash +agentuity cloud env import .env.staging +``` + + +Use `cloud secret` for sensitive values like API keys. Use `cloud env` for non-sensitive configuration. + + +## Secrets + +Manage sensitive values like API keys and tokens. Commands mirror `cloud env`. + +### List Secrets + +```bash +agentuity cloud secret list +``` + +### Get a Secret + +```bash +agentuity cloud secret get API_KEY +``` + +### Set a Secret + +```bash +agentuity cloud secret set API_KEY "sk_live_..." +agentuity cloud secret set DATABASE_URL "postgresql://..." +``` + +### Delete a Secret + +```bash +agentuity cloud secret delete OLD_TOKEN +``` + +### Push/Pull + +```bash +# Push from local file +agentuity cloud secret push + +# Pull to local file +agentuity cloud secret pull +agentuity cloud secret pull --force +``` + +### Import from File + +```bash +agentuity cloud secret import .env.secrets +``` + + +Access secrets in agents via `process.env`: + +```typescript +const apiKey = process.env.API_KEY; +``` + +Secrets are injected at runtime and never logged. + + +## API Keys + +Create and manage API keys for programmatic access to your project. + +### Create an API Key + +```bash +# With 1 year expiration +agentuity cloud apikey create --name "Production Key" --expires-at 1y + +# With 30 day expiration +agentuity cloud apikey create --name "Short-lived Key" --expires-at 30d + +# With specific date +agentuity cloud apikey create --name "Q1 Key" --expires-at 2026-03-31T23:59:59Z + +# Skip confirmation prompt +agentuity cloud apikey create --name "CI/CD Key" --expires-at 90d --confirm +``` + + +The API key value is shown only once during creation. Copy it immediately; you cannot retrieve it later. + + +### List API Keys + +```bash +agentuity cloud apikey list +``` + +### Get API Key Details + +```bash +agentuity cloud apikey get +``` + +This shows metadata (name, expiration) but not the key value. + +### Delete an API Key + +```bash +agentuity cloud apikey delete +``` + + +API keys authenticate requests to Agentuity APIs. Store them in your `.env` as `AGENTUITY_SDK_KEY`: + +```bash +AGENTUITY_SDK_KEY=agt_... +``` + + +## Database (Coming Soon) + +The CLI includes `cloud db` commands for database operations (list, create, delete, SQL queries). Documentation is coming soon. + +## Next Steps + +- [Getting Started with the CLI](/v1/Reference/CLI/getting-started): Install and authenticate +- [Key-Value Storage](/v1/Build/Storage/key-value): Programmatic KV access in agents +- [Object Storage](/v1/Build/Storage/object): Upload files from agents +- [Vector Storage](/v1/Build/Storage/vector): Semantic search in agents diff --git a/content/v1/Reference/CLI/debugging.mdx b/content/v1/Reference/CLI/debugging.mdx new file mode 100644 index 00000000..4e38735c --- /dev/null +++ b/content/v1/Reference/CLI/debugging.mdx @@ -0,0 +1,290 @@ +--- +title: Debugging Deployments +description: SSH into containers, inspect sessions, and troubleshoot issues in your deployed agents. +--- + +Debug live deployments by SSHing into containers, transferring files, inspecting sessions and threads, and viewing deployment logs. + +## SSH Access + +Connect to your deployed containers for debugging: + +```bash +# SSH into current project +agentuity cloud ssh + +# SSH into specific project +agentuity cloud ssh proj_abc123xyz + +# SSH into specific deployment +agentuity cloud ssh dep_abc123xyz + +# Run a command and exit +agentuity cloud ssh 'ps aux' + +# Run command on specific project +agentuity cloud ssh proj_abc123xyz 'tail -f /var/log/app.log' + +# Show SSH command without executing +agentuity cloud ssh --show +``` + + +Add your SSH public key before connecting: + +```bash +agentuity auth ssh add ~/.ssh/id_rsa.pub +``` + +See [Getting Started](/v1/Reference/CLI/getting-started) for authentication setup. + + +**Interactive session:** +```bash +agentuity cloud ssh + +# Now you're in the container +cd /app +ls -la +cat .env +exit +``` + +## File Transfer + +Use SCP to upload and download files from deployments: + +```bash +# Upload file to home directory +agentuity cloud scp upload ./config.json + +# Upload to specific path +agentuity cloud scp upload ./config.json /app/config.json + +# Upload to specific project +agentuity cloud scp upload ./config.json --identifier=proj_abc123xyz + +# Upload multiple files +agentuity cloud scp upload ./logs/*.log ~/logs/ + +# Download file to current directory +agentuity cloud scp download /var/log/app.log + +# Download to specific path +agentuity cloud scp download /var/log/app.log ./logs/ + +# Download from specific project +agentuity cloud scp download /app/config.json --identifier=proj_abc123xyz + +# Download multiple files +agentuity cloud scp download ~/logs/*.log ./logs/ +``` + +**Common use cases:** +- Upload configuration files for testing +- Download logs for local analysis +- Transfer debug scripts +- Backup application state + +## Agent Inspection + +List and inspect agents deployed in your project: + +```bash +# List all agents in the project +agentuity cloud agent list + +# List agents for a specific project +agentuity cloud agent list --project-id=proj_abc123xyz + +# Get details for a specific agent +agentuity cloud agent get agent_abc123xyz + +# View agent input/output schema +agentuity cloud agent schema agent_abc123xyz +``` + +**Agent details include:** +- Agent ID and name +- Description and metadata +- Associated routes +- Schema definitions (input/output types) + +Use agent inspection to: +- Verify deployed agent configuration +- Debug schema mismatches +- Explore available agents in a project +- Generate client code from schemas + +## Session Logs + +Inspect individual agent sessions, including request details, timeline, and logs: + +```bash +# List recent sessions +agentuity cloud session list + +# List 25 most recent sessions +agentuity cloud session list --count=25 + +# Filter by project +agentuity cloud session list --project-id=proj_abc123xyz + +# Filter by deployment +agentuity cloud session list --deployment-id=dep_abc123xyz + +# Only successful sessions +agentuity cloud session list --success=true + +# Only failed sessions +agentuity cloud session list --success=false + +# Filter by specific trigger type +agentuity cloud session list --trigger=api + +# Filter by specific environment +agentuity cloud session list --env=production +``` + +**Get session details:** +```bash +# Full session information with timeline +agentuity cloud session get sess_abc123xyz +``` + +This shows: +- Request method, URL, and headers +- Success/failure status and error messages +- Duration and timing information +- Agent execution timeline +- Eval runs (if configured) + +**View session logs:** +```bash +# View logs for specific session +agentuity cloud session logs sess_abc123xyz + +# Hide timestamps +agentuity cloud session logs sess_abc123xyz --no-timestamps +``` + + +Session logs show output for a single request. Deployment logs show all output from a deployment, including startup, errors, and background tasks. + + +## Thread Inspection + +List and manage conversation threads. For details on how threads work in agents, see [State Management](/v1/Build/Agents/state-management). + +```bash +# List recent threads +agentuity cloud thread list + +# List 25 most recent threads +agentuity cloud thread list --count=25 + +# Filter by project +agentuity cloud thread list --project-id=proj_abc123xyz + +# Get thread details +agentuity cloud thread get thrd_abc123xyz + +# Delete a thread +agentuity cloud thread delete thrd_abc123xyz +``` + +**Thread details include:** +- Thread ID and timestamps +- Associated project +- User data (metadata) +- Deletion status + +Use thread inspection to: +- Debug conversation state issues +- Clean up old threads +- Verify thread metadata +- Track thread lifecycle + +## Deployment Logs + +Stream logs from a specific deployment: + +```bash +# View deployment logs +agentuity cloud deployment logs dep_abc123xyz + +# Limit to 50 log entries +agentuity cloud deployment logs dep_abc123xyz --limit=50 + +# Hide timestamps +agentuity cloud deployment logs dep_abc123xyz --no-timestamps + +# Specify project explicitly +agentuity cloud deployment logs dep_abc123xyz --project-id=proj_abc123xyz +``` + +**Log output includes:** +- Severity levels (INFO, WARN, ERROR) +- Timestamps +- Log messages +- Stack traces for errors + +**Use deployment logs to:** +- Monitor startup issues +- Debug background tasks +- Track errors across requests +- Analyze performance issues + +## JSON Output for Scripting + +All commands support `--json` for machine-readable output. Combined with [jq](/v1/Reference/CLI/getting-started#json-output), you can filter and transform results: + +```bash +# Get session list as JSON +agentuity --json cloud session list --count=100 > sessions.json + +# Get thread details as JSON +agentuity --json cloud thread get thrd_abc123xyz + +# Process with jq +agentuity --json cloud session list | jq '.[] | select(.success == false)' + +# Find failed sessions in last hour +agentuity --json cloud session list --count=100 | \ + jq '.[] | select(.success == false) | {id, url, error}' + +# Count sessions by trigger type +agentuity --json cloud session list --count=100 | \ + jq 'group_by(.trigger) | map({trigger: .[0].trigger, count: length})' +``` + +**Scripting examples:** + +Here are some practical scripts for monitoring and automation: + +```bash +#!/bin/bash +# Monitor for failures and send alerts + +FAILED=$(agentuity --json cloud session list --success=false --count=10) +COUNT=$(echo "$FAILED" | jq 'length') + +if [ "$COUNT" -gt 5 ]; then + echo "Alert: $COUNT failed sessions detected" + echo "$FAILED" | jq '.[] | {id, url, error}' +fi +``` + +```bash +#!/bin/bash +# Export thread user data + +agentuity --json cloud thread list --count=100 | \ + jq -r '.[] | [.id, .user_data] | @csv' > threads.csv +``` + +## Next Steps + +- [Managing Cloud Data](/v1/Reference/CLI/data-management): Inspect storage, env vars, and secrets +- [Deploying to the Cloud](/v1/Reference/CLI/deployment): Deploy and manage projects +- [Logging](/v1/Build/Observability/logging): Configure logging in your agents diff --git a/content/v1/Reference/CLI/deployment.mdx b/content/v1/Reference/CLI/deployment.mdx new file mode 100644 index 00000000..fc396c58 --- /dev/null +++ b/content/v1/Reference/CLI/deployment.mdx @@ -0,0 +1,395 @@ +--- +title: Deploying to the Cloud +description: Deploy your agents to Agentuity Cloud with automatic infrastructure provisioning. +--- + +Deploy your Agentuity project to the cloud with a single command. The platform handles infrastructure, scaling, and monitoring automatically. + +## Deploy Your Project + +```bash +agentuity deploy +``` + +The deploy command: +1. Syncs environment variables from `.env.production` (or `.env` as fallback) +2. Builds and packages your project +3. Encrypts and uploads the deployment bundle securely +4. Provisions your deployment on Agentuity's infrastructure +5. Activates your deployment + + +After deployment, view your environment variables and secrets in the Agentuity Console under **Project > Settings**. + + +**Example output:** + +``` +✓ Sync Env & Secrets +✓ Create Deployment +✓ Build, Verify and Package +✓ Encrypt and Upload Deployment +✓ Provision Deployment + +Your project was deployed! + +→ Deployment ID: dep_abc123xyz +→ Deployment URL: https://dep-abc123xyz.agentuity.cloud +→ Project URL: https://proj-456def.agentuity.cloud +``` + +## Deployment URLs + +Each deployment gets two URLs: + +**Deployment URL** (`dep_xxx.agentuity.cloud`): +- Unique URL for this specific deployment +- Persists even after new deployments +- Use for testing a specific version + +**Project URL** (`proj_xxx.agentuity.cloud`): +- Always points to the active deployment +- Updates automatically when you deploy +- Use for stable endpoints and webhooks + +```bash +# Deploy creates both URLs +agentuity deploy + +# Deployment URL: https://dep-abc123.agentuity.cloud +# Project URL: https://proj-456def.agentuity.cloud +``` + + +Both URLs are automatically provisioned and support HTTPS. The project URL automatically routes to the active deployment. + + +## Tagging Deployments + +Add tags to label deployments: + +```bash +# Single tag +agentuity deploy --tag staging + +# Multiple tags +agentuity deploy --tag staging --tag hotfix-123 +``` + +Tags appear in deployment lists and help organize versions: + +```bash +agentuity cloud deployment list +# Shows tags column for each deployment +``` + +## Viewing Deployments + +List recent deployments: + +```bash +# Show 10 most recent +agentuity cloud deployment list + +# Custom count +agentuity cloud deployment list --count=25 + +# For a specific project +agentuity cloud deployment list --project-id=proj_abc123xyz +``` + +**Example output:** + +| ID | State | Active | Created | Message | Tags | +|----|-------|--------|---------|---------|------| +| dep_abc123 | completed | Yes | 12/1/25, 3:45 PM | | staging | +| dep_def456 | completed | | 12/1/25, 2:30 PM | | v1.2.0 | +| dep_ghi789 | completed | | 11/30/25, 5:00 PM | | | + +## Deployment Details + +View detailed information about a deployment: + +```bash +# Show deployment details +agentuity cloud deployment show dep_abc123xyz + +# For specific project +agentuity cloud deployment show dep_abc123xyz --project-id=proj_abc123 +``` + +**Output includes:** +- Deployment ID, state, and creation time +- Active status +- Tags and custom domains +- Cloud region +- Git metadata (repo, commit, branch, PR) +- Build information (SDK version, runtime, platform) + +``` +ID: dep_abc123xyz +Project: proj_456def +State: completed +Active: Yes +Created: 12/1/25, 3:45 PM +Tags: staging, hotfix-123 + +Git Information + Repo: myorg/myapp + Branch: main + Commit: a1b2c3d + Message: Fix authentication bug + +Build Information + Agentuity: 1.0.0 + Bun: 1.1.40 + Platform: linux + Arch: x64 +``` + +## Viewing Logs + +Fetch logs for a deployment: + +```bash +# View logs +agentuity cloud deployment logs dep_abc123xyz + +# Limit log entries +agentuity cloud deployment logs dep_abc123xyz --limit=50 + +# Hide timestamps +agentuity cloud deployment logs dep_abc123xyz --no-timestamps + +# For specific project +agentuity cloud deployment logs dep_abc123xyz --project-id=proj_abc123 +``` + +Logs show severity (INFO, WARN, ERROR) and message body with color-coded output. + + +For live streaming logs, use SSH access. See [Debugging](/v1/Reference/CLI/debugging) for SSH setup. + + +## Rolling Back + +Revert to a previous deployment: + +```bash +agentuity cloud deployment rollback +``` + +The command: +1. Finds the previous completed deployment +2. Prompts for confirmation +3. Activates the previous deployment +4. Updates the project URL + +``` +Rollback to deployment dep_def456? (y/N): y +✓ Rolled back to deployment dep_def456 +``` + +Use for specific projects: + +```bash +agentuity cloud deployment rollback --project-id=proj_abc123xyz +``` + + +Rollback activates the most recent completed deployment before the current one. The current deployment remains available at its deployment URL but is no longer active. + + +## Undeploying + +Stop the active deployment: + +```bash +# With confirmation prompt +agentuity cloud deployment undeploy + +# Skip confirmation +agentuity cloud deployment undeploy --force + +# For specific project +agentuity cloud deployment undeploy --project-id=proj_abc123xyz +``` + +After undeploying: +- The project URL becomes unavailable +- All deployment-specific URLs remain accessible +- Previous deployments are not deleted +- You can redeploy or rollback at any time + + +Undeploying stops the active deployment immediately. Your project URL will return 404 until you deploy or rollback. + + +## Removing Deployments + +Permanently delete a deployment: + +```bash +# Remove a specific deployment +agentuity cloud deployment remove dep_abc123xyz + +# For specific project +agentuity cloud deployment remove dep_abc123xyz --project-id=proj_abc123xyz +``` + + +Removing a deployment permanently deletes it. You cannot rollback to a removed deployment. + + +## Custom Domains + +You can configure custom domains using either the configuration file or CLI commands. + +### Option 1: Configuration File + +Configure custom domains in `agentuity.json`: + +```json +{ + "projectId": "proj_abc123xyz", + "orgId": "org_def456", + "region": "us-east-1", + "deployment": { + "domains": ["api.example.com", "app.example.com"] + } +} +``` + + +Available regions are fetched from the API at deployment time. If multiple regions are available, the CLI prompts you to select one. + + +### DNS Configuration + +Add a CNAME record for each custom domain. A CNAME record tells DNS to resolve your domain to Agentuity's servers: + +``` +Type: CNAME +Name: api.example.com +Value: p.agentuity.cloud +TTL: 600 +``` + +The `p` value is your project's unique identifier, shown when you run `agentuity deploy` or in the Console. + +The CLI validates DNS records during deployment: + +```bash +agentuity deploy +``` + +``` +✓ Validate Custom Domains: api.example.com, app.example.com +✓ Sync Env & Secrets +... +``` + + +Deployment fails if DNS records are missing or incorrect. The CLI shows the required CNAME value in the error message. + + +### Multiple Domains + +Custom domains replace the default URLs: + +```json +{ + "deployment": { + "domains": ["api.example.com"] + } +} +``` + +After deployment with custom domains, the CLI only shows custom URLs: + +``` +→ Deployment ID: dep_abc123xyz +→ Deployment URL: https://api.example.com +``` + +The project URL and deployment-specific URLs still exist but custom domains take precedence in the output. + +### Option 2: CLI Commands + +Manage custom domains directly from the CLI: + +```bash +# List configured domains +agentuity cloud domain list + +# Add a domain +agentuity cloud domain add api.example.com + +# Remove a domain +agentuity cloud domain remove api.example.com + +# Verify DNS configuration +agentuity cloud domain verify api.example.com +``` + + +Use the configuration file for domains that should persist across deployments. Use CLI commands for quick testing or temporary domains. + + +## Environment Variables + +The deploy command syncs variables from `.env.production` (or `.env` as fallback): + +```bash +# .env.production +DATABASE_URL=postgres://... +WEBHOOK_SECRET=secret123 +MY_CUSTOM_API_KEY=xxx +``` + + +If you're using the [AI Gateway](/v1/Build/Agents/ai-gateway), you don't need to include provider API keys (like `OPENAI_API_KEY`). The gateway handles authentication automatically. Only include provider keys if you're bypassing the gateway. + + +Variables are automatically: +- Filtered (removes `AGENTUITY_` prefixed keys) +- Categorized (regular env vs secrets based on naming) +- Encrypted (secrets only) +- Synced to cloud before the build step + + +Variables with suffixes like `_SECRET`, `_KEY`, `_TOKEN`, `_PASSWORD`, or `_PRIVATE` are automatically encrypted as secrets. All other variables are stored as regular environment variables. + + +## Deploy Options + +| Option | Description | +|--------|-------------| +| `--tag ` | Add deployment tag (repeatable) | +| `-f, --force` | Force deployment without confirmation | +| `--dry-run` | Simulate deployment without executing | +| `--log-level debug` | Show verbose output | +| `--project-id ` | Deploy specific project | + +```bash +# Verbose deployment +agentuity deploy --log-level=debug + +# Multiple tags +agentuity deploy --tag staging --tag v1.2.0 + +# Specific project +agentuity deploy --project-id=proj_abc123xyz + +# Simulate deployment +agentuity deploy --dry-run + +# Force deployment +agentuity deploy --force +``` + +## Next Steps + +- [Debugging Deployments](/v1/Reference/CLI/debugging): SSH access and debugging tools +- [Logging](/v1/Build/Observability/logging): Configure logging in your agents +- [Key-Value Storage](/v1/Build/Storage/key-value): Store persistent data diff --git a/content/v1/Reference/CLI/development.mdx b/content/v1/Reference/CLI/development.mdx new file mode 100644 index 00000000..df1a5775 --- /dev/null +++ b/content/v1/Reference/CLI/development.mdx @@ -0,0 +1,214 @@ +--- +title: Local Development +description: Run the development server with hot reload, local mode, and the interactive Workbench. +--- + +Run your Agentuity project locally with automatic hot reload, type checking, and the interactive Workbench UI. + +## Starting the Dev Server + +```bash +agentuity dev +``` + +The server starts on port 3500 by default with: +- Hot reload on file changes +- TypeScript type checking +- Public URL tunneling (optional, for webhooks and sharing) +- Interactive keyboard shortcuts + +## Dev Server Options + +| Option | Default | Description | +|--------|---------|-------------| +| `--port` | 3500 | TCP port for the dev server | +| `--local` | false | Use local services instead of cloud | +| `--public` | true | Enable public URL tunneling | +| `--no-public` | - | Disable public URL tunneling | +| `--interactive` | true | Enable interactive keyboard shortcuts | + +```bash +# Custom port +agentuity dev --port 8080 + +# Local mode (offline development) +agentuity dev --local + +# Disable public URL +agentuity dev --no-public + +# Non-interactive mode (useful for CI/CD) +agentuity dev --no-interactive +``` + +## Keyboard Shortcuts + +Press keys during development to control the server: + +| Key | Action | +|-----|--------| +| `h` | Show help | +| `c` | Clear console | +| `r` | Restart server | +| `o` | Show routes | +| `a` | Show agents | +| `q` | Quit | + +## Local Mode + +Use `--local` to develop offline without cloud services: + +```bash +agentuity dev --local +``` + + +Local mode disables cloud services (KV, Vector, Object Storage). Use when developing without internet or testing with mock data. + + +**What happens in local mode:** +- No connection to Agentuity cloud +- Storage APIs disabled unless you provide [custom implementations](/v1/Build/Storage/custom) +- Public URL tunneling disabled +- Requires your own API keys for AI providers (in `.env`), since the [AI Gateway](/v1/Build/Agents/ai-gateway) is not available + +**Bring Your Own Storage:** + +```typescript +// app.ts +import { createApp } from '@agentuity/runtime'; +import { MyCustomKV } from './storage'; + +const app = createApp({ + services: { + useLocal: true, + keyvalue: new MyCustomKV(), // Your implementation + }, +}); +``` + +See [Custom Storage](/v1/Build/Storage/custom) for implementation details. + +## Public URLs + +Enable public URLs to share your local dev server or receive webhooks: + +```bash +# Public URL enabled by default +agentuity dev + +# Or explicitly +agentuity dev --public +``` + + +The `--public` flag creates a secure tunnel through Agentuity's edge network. Your local server gets a public HTTPS URL for testing webhooks, sharing with teammates, or external integrations. + + +**Example output:** +``` +⨺ Agentuity DevMode + Local: http://127.0.0.1:3500 + Public: https://abc123.devmode.agentuity.com + + Press h for keyboard shortcuts +``` + +**Example use cases:** +- Testing Slack, Discord, or Twilio webhooks +- Sharing with team members +- Testing from mobile devices +- OAuth callback URLs + +## Workbench UI + +Access the interactive Workbench at `http://localhost:3500/workbench` to test agents visually. + +**Features:** +- Test agents with custom inputs or generated data (based on the agent's schema) +- View request/response data +- Inspect agent metadata +- Explore available routes + + +Enable the Workbench in your `app.ts`: + +```typescript +import { createApp, createWorkbench } from '@agentuity/runtime'; + +const app = createApp(); +const workbench = createWorkbench({ route: '/workbench' }); + +export default app; +``` + + +## Building Your Project + +Bundle your project for deployment: + +```bash +agentuity build +``` + +**What happens during build:** +1. TypeScript compilation +2. Bundle agents, routes, and frontend +3. Generate registry and types +4. Type check with `tsc` +5. Create `.agentuity/` output directory + +### Build Options + +| Option | Default | Description | +|--------|---------|-------------| +| `--dev` | false | Enable development mode | +| `--outdir` | `.agentuity` | Output directory | +| `--skip-type-check` | false | Skip TypeScript type checking | + +```bash +# Skip type checking (faster builds) +agentuity build --skip-type-check + +# Custom output directory +agentuity build --outdir ./dist +``` + + +Build fails if TypeScript errors are detected. Fix type errors or use `--skip-type-check` to override (not recommended for deployments). + + +## Hot Reload Behavior + +The dev server watches for file changes and automatically: +- Rebuilds on source file changes (`.ts`, `.tsx`, `.js`, `.jsx`) +- Runs TypeScript type checking +- Restarts the server +- Preserves background tasks + +**Ignored files:** +- `node_modules/` +- `.agentuity/` (build output) +- Generated files (`*.generated.ts`) +- Temporary files + +**Cooldown period:** 500ms after build completes to prevent restart loops. + +## Development vs Production + +Understanding the differences between local development and deployed production: + +| Aspect | Local (`agentuity dev`) | Production (`agentuity deploy`) | +|--------|------------------------|--------------------------------| +| **Storage** | Cloud services (or local with `--local`) | Cloud services always | +| **AI Gateway** | Available (or BYO keys with `--local`) | Available always | +| **URL** | `localhost:3500` + optional public tunnel | `*.agentuity.cloud` or custom domain | +| **Hot Reload** | Yes | No (redeploy required) | +| **Debugging** | Local logs, Workbench | SSH access, cloud logs | +| **Environment** | `.env` | `.env.production` (synced to cloud) | + +## Next Steps + +- [Deploying to the Cloud](/v1/Reference/CLI/deployment): Deploy to Agentuity Cloud +- [Creating Agents](/v1/Build/Agents/creating-agents): Create your first agent +- [HTTP Routes](/v1/Build/Routes-Triggers/http-routes): Add HTTP endpoints diff --git a/content/v1/Reference/CLI/getting-started.mdx b/content/v1/Reference/CLI/getting-started.mdx new file mode 100644 index 00000000..fb7fa0c3 --- /dev/null +++ b/content/v1/Reference/CLI/getting-started.mdx @@ -0,0 +1,308 @@ +--- +title: Getting Started with the CLI +description: Install the Agentuity CLI and authenticate to start building agents. +--- + +# Getting Started with the CLI + +Install the Agentuity CLI to create projects, deploy agents, and manage your account from the terminal. + +The v1 CLI is designed to be your primary interface for building and managing Agentuity projects. You can create agents, inspect sessions, manage storage, debug deployments, and deploy — all without leaving your terminal. The [Console](https://app.agentuity.com) stays fully synced for when you prefer a visual interface. + +## Installation + +Install the CLI globally with bun or npm: + +```bash +# Using Bun (recommended) +bun add -g @agentuity/cli + +# Using npm +npm install -g @agentuity/cli +``` + +Verify the installation: + +```bash +agentuity --version +``` + + +Bun 1.3.0 or higher is required to run the CLI. + + +## Authentication + +### Login to Your Account + +Authenticate using a browser-based login flow: + +```bash +agentuity auth login +# or simply +agentuity login +``` + +This command: +1. Generates a one-time password and opens your browser +2. Prompts you to authenticate in the web app +3. Saves your API credentials locally + +Check your authentication status: + +```bash +agentuity auth whoami +``` + +### Create a New Account + +Sign up for a new Agentuity account directly from the CLI: + +```bash +agentuity auth signup +``` + +This opens your browser to complete account creation, then automatically saves your credentials locally. + +### Logout + +Clear your local credentials: + +```bash +agentuity auth logout +# or simply +agentuity logout +``` + +## SSH Key Management + +Add SSH keys to your account for secure container access during development and debugging. + +### List SSH Keys + +View all SSH keys on your account: + +```bash +agentuity auth ssh list +``` + +### Add an SSH Key + +Add a new SSH key interactively (automatically discovers keys in `~/.ssh/`): + +```bash +agentuity auth ssh add +``` + +Add a specific key file: + +```bash +agentuity auth ssh add --file ~/.ssh/id_ed25519.pub +``` + +Pipe a key from stdin: + +```bash +cat ~/.ssh/id_rsa.pub | agentuity auth ssh add +``` + +### Remove an SSH Key + +Remove keys interactively: + +```bash +agentuity auth ssh delete +``` + +Remove a specific key by fingerprint: + +```bash +agentuity auth ssh delete +``` + + +SSH keys enable secure access to deployed agent containers for debugging and log inspection. See [Debugging Deployments](/v1/Reference/CLI/debugging) for details. + + +## Creating Projects + +### Create a New Project + +The quickest way to get started is the interactive project creation: + +```bash +agentuity project create +# or simply +agentuity create +``` + +Create a project with a specific name: + +```bash +agentuity project create --name my-agent +``` + +Create a project in a specific directory: + +```bash +agentuity project create --name my-agent --dir ~/projects/agents +``` + + +Currently, the default template includes React UI components and example agents. Additional templates will be available in future releases. + + +### Project Creation Options + +- `--name `: Project name +- `--dir `: Directory to create the project in +- `--template