Building Your First ARAL Agent
Prerequisites: Basic knowledge of TypeScript or Python, Node.js 18+ or Python 3.10+
This guide walks you through building a complete ARAL-CORE compliant agent from scratch. You’ll create a customer service agent with memory, capabilities, and a defined persona.
What You’ll Build
Section titled “What You’ll Build”By the end of this guide, you’ll have:
- ✅ A fully functional ARAL-CORE agent (L1-L5)
- ✅ Persistent memory with context awareness
- ✅ Custom capabilities for actions
- ✅ A professional customer service persona
- ✅ Production-ready error handling
Step 1: Project Setup
Section titled “Step 1: Project Setup”# Create new projectmkdir my-first-aral-agentcd my-first-aral-agentnpm init -y
# Install ARAL SDKnpm install @aral/sdk @aral/runtime @aral/memory @aral/capabilities
# Install dependenciesnpm install typescript @types/node tsxnpx tsc --init# Create virtual environmentpython -m venv venvsource venv/bin/activate # On Windows: venv\Scripts\activate
# Install ARAL SDKpip install aral-sdk
# Create project structuremkdir -p src/{runtime,memory,capabilities,reasoning,persona}touch src/main.pyStep 2: Initialize the Runtime (Layer 1)
Section titled “Step 2: Initialize the Runtime (Layer 1)”The Runtime Layer manages the execution lifecycle of your agent.
import { Runtime, RuntimeConfig } from '@aral/runtime';
export class AgentRuntime { private runtime: Runtime;
constructor() { this.runtime = new Runtime({ maxConcurrentTasks: 10, resourceLimits: { cpu: { percent: 80 }, memory: { mb: 512 }, storage: { mb: 1024 } }, errorHandling: { maxRetries: 3, retryDelay: 1000, strategy: 'exponential-backoff' } }); }
async start(): Promise<void> { await this.runtime.initialize(); console.log('✅ Runtime initialized'); }
async stop(): Promise<void> { await this.runtime.shutdown(); console.log('✅ Runtime stopped'); }
getRuntime(): Runtime { return this.runtime; }}from aral.runtime import Runtime, RuntimeConfig
class AgentRuntime: def __init__(self): self.runtime = Runtime(RuntimeConfig( max_concurrent_tasks=10, resource_limits={ 'cpu': {'percent': 80}, 'memory': {'mb': 512}, 'storage': {'mb': 1024} }, error_handling={ 'max_retries': 3, 'retry_delay': 1000, 'strategy': 'exponential-backoff' } ))
async def start(self): await self.runtime.initialize() print('✅ Runtime initialized')
async def stop(self): await self.runtime.shutdown() print('✅ Runtime stopped')
def get_runtime(self): return self.runtimeStep 3: Add Memory (Layer 2)
Section titled “Step 3: Add Memory (Layer 2)”Memory enables your agent to maintain context across conversations.
import { MemoryManager, ShortTermMemory, LongTermMemory } from '@aral/memory';
export class AgentMemory { private memoryManager: MemoryManager; private stm: ShortTermMemory; private ltm: LongTermMemory;
constructor() { // Short-term memory (conversation context) this.stm = new ShortTermMemory({ maxTokens: 4000, pruningStrategy: 'summarize' });
// Long-term memory (persistent storage) this.ltm = new LongTermMemory({ store: 'postgresql', connection: process.env.DATABASE_URL, retention: { days: 90 } });
this.memoryManager = new MemoryManager({ shortTerm: this.stm, longTerm: this.ltm }); }
async remember(userId: string, message: string): Promise<void> { await this.stm.add({ userId, content: message, timestamp: Date.now() });
// Persist important information await this.ltm.store(userId, { type: 'user_interaction', content: message, timestamp: Date.now() }); }
async recall(userId: string, query?: string): Promise<any[]> { const stmContext = await this.stm.retrieve(userId); const ltmContext = await this.ltm.search(userId, query);
return [...stmContext, ...ltmContext]; }}from aral.memory import MemoryManager, ShortTermMemory, LongTermMemoryimport osfrom datetime import datetime
class AgentMemory: def __init__(self): # Short-term memory (conversation context) self.stm = ShortTermMemory( max_tokens=4000, pruning_strategy='summarize' )
# Long-term memory (persistent storage) self.ltm = LongTermMemory( store='postgresql', connection=os.getenv('DATABASE_URL'), retention={'days': 90} )
self.memory_manager = MemoryManager( short_term=self.stm, long_term=self.ltm )
async def remember(self, user_id: str, message: str): await self.stm.add({ 'user_id': user_id, 'content': message, 'timestamp': datetime.now().timestamp() })
# Persist important information await self.ltm.store(user_id, { 'type': 'user_interaction', 'content': message, 'timestamp': datetime.now().timestamp() })
async def recall(self, user_id: str, query: str = None): stm_context = await self.stm.retrieve(user_id) ltm_context = await self.ltm.search(user_id, query)
return [*stm_context, *ltm_context]Step 4: Define Capabilities (Layer 3)
Section titled “Step 4: Define Capabilities (Layer 3)”Capabilities are the actions your agent can perform.
import { CapabilityRegistry, Action } from '@aral/capabilities';
export class AgentCapabilities { private registry: CapabilityRegistry;
constructor() { this.registry = new CapabilityRegistry(); this.registerCapabilities(); }
private registerCapabilities(): void { // Check Order Status this.registry.register({ name: 'check_order_status', description: 'Check the status of a customer order', parameters: { type: 'object', properties: { orderId: { type: 'string', description: 'Order ID' } }, required: ['orderId'] }, handler: async (params) => { // Simulate API call return { orderId: params.orderId, status: 'shipped', estimatedDelivery: '2025-01-20' }; } });
// Process Refund this.registry.register({ name: 'process_refund', description: 'Process a refund for an order', parameters: { type: 'object', properties: { orderId: { type: 'string' }, reason: { type: 'string' } }, required: ['orderId', 'reason'] }, handler: async (params) => { return { refundId: `RF-${Date.now()}`, amount: 99.99, status: 'processing' }; } });
// Send Email this.registry.register({ name: 'send_email', description: 'Send an email to the customer', parameters: { type: 'object', properties: { email: { type: 'string', format: 'email' }, subject: { type: 'string' }, body: { type: 'string' } }, required: ['email', 'subject', 'body'] }, handler: async (params) => { console.log(`📧 Sending email to ${params.email}`); return { messageId: `MSG-${Date.now()}`, sent: true }; } }); }
async execute(actionName: string, params: any): Promise<any> { return await this.registry.execute(actionName, params); }
listCapabilities(): string[] { return this.registry.list(); }}from aral.capabilities import CapabilityRegistry, Actionfrom datetime import datetime
class AgentCapabilities: def __init__(self): self.registry = CapabilityRegistry() self._register_capabilities()
def _register_capabilities(self): # Check Order Status @self.registry.register( name='check_order_status', description='Check the status of a customer order', parameters={ 'type': 'object', 'properties': { 'order_id': {'type': 'string', 'description': 'Order ID'} }, 'required': ['order_id'] } ) async def check_order(params): # Simulate API call return { 'order_id': params['order_id'], 'status': 'shipped', 'estimated_delivery': '2025-01-20' }
# Process Refund @self.registry.register( name='process_refund', description='Process a refund for an order', parameters={ 'type': 'object', 'properties': { 'order_id': {'type': 'string'}, 'reason': {'type': 'string'} }, 'required': ['order_id', 'reason'] } ) async def process_refund(params): return { 'refund_id': f"RF-{int(datetime.now().timestamp())}", 'amount': 99.99, 'status': 'processing' }
# Send Email @self.registry.register( name='send_email', description='Send an email to the customer', parameters={ 'type': 'object', 'properties': { 'email': {'type': 'string', 'format': 'email'}, 'subject': {'type': 'string'}, 'body': {'type': 'string'} }, 'required': ['email', 'subject', 'body'] } ) async def send_email(params): print(f"📧 Sending email to {params['email']}") return { 'message_id': f"MSG-{int(datetime.now().timestamp())}", 'sent': True }
async def execute(self, action_name: str, params: dict): return await self.registry.execute(action_name, params)
def list_capabilities(self): return self.registry.list()Step 5: Implement Reasoning (Layer 4)
Section titled “Step 5: Implement Reasoning (Layer 4)”The Reasoning Layer handles decision-making and planning.
import { ReasoningEngine, LLMProvider } from '@aral/reasoning';import { AgentCapabilities } from '../capabilities/agent-capabilities';
export class AgentReasoning { private engine: ReasoningEngine;
constructor( private capabilities: AgentCapabilities, llmApiKey: string ) { this.engine = new ReasoningEngine({ provider: new LLMProvider({ model: 'gpt-4', apiKey: llmApiKey, temperature: 0.7 }), planning: { maxSteps: 5, strategy: 'chain-of-thought' } }); }
async reason( userMessage: string, context: any[] ): Promise<{ response: string; actions: any[] }> { // Build prompt with context const prompt = this.buildPrompt(userMessage, context);
// Get LLM response const result = await this.engine.process(prompt, { availableActions: this.capabilities.listCapabilities() });
// Execute actions if needed const actions = []; for (const action of result.actions) { const actionResult = await this.capabilities.execute( action.name, action.parameters ); actions.push(actionResult); }
return { response: result.response, actions }; }
private buildPrompt(message: string, context: any[]): string { const contextStr = context .map(c => `${c.role}: ${c.content}`) .join('\n');
return `Context:${contextStr}
User: ${message}
Available actions: ${this.capabilities.listCapabilities().join(', ')}
Respond naturally and use actions when needed.`; }}from aral.reasoning import ReasoningEngine, LLMProvider
class AgentReasoning: def __init__(self, capabilities, llm_api_key: str): self.capabilities = capabilities self.engine = ReasoningEngine( provider=LLMProvider( model='gpt-4', api_key=llm_api_key, temperature=0.7 ), planning={ 'max_steps': 5, 'strategy': 'chain-of-thought' } )
async def reason(self, user_message: str, context: list): # Build prompt with context prompt = self._build_prompt(user_message, context)
# Get LLM response result = await self.engine.process( prompt, available_actions=self.capabilities.list_capabilities() )
# Execute actions if needed actions = [] for action in result['actions']: action_result = await self.capabilities.execute( action['name'], action['parameters'] ) actions.append(action_result)
return { 'response': result['response'], 'actions': actions }
def _build_prompt(self, message: str, context: list) -> str: context_str = '\n'.join( f"{c['role']}: {c['content']}" for c in context )
return f"""Context:{context_str}
User: {message}
Available actions: {', '.join(self.capabilities.list_capabilities())}
Respond naturally and use actions when needed."""Step 6: Define Persona (Layer 5)
Section titled “Step 6: Define Persona (Layer 5)”The Persona Layer defines your agent’s identity and behavior.
import { Persona, PersonaConfig } from '@aral/persona';
export const CustomerServicePersona: PersonaConfig = { identity: { name: 'Alex', role: 'Customer Service Agent', description: 'Friendly and professional customer service representative' },
personality: { traits: { friendliness: 0.9, professionalism: 0.95, patience: 1.0, empathy: 0.9, efficiency: 0.85 } },
tone: { formality: 'professional-friendly', emoji: 'minimal', language: 'clear-concise' },
constraints: { topics: { allowed: [ 'orders', 'refunds', 'shipping', 'product_info', 'account_help' ], forbidden: ['politics', 'medical_advice', 'legal_advice'] },
behaviors: { must: [ 'Always greet users warmly', 'Confirm understanding before acting', 'Provide clear next steps', 'End with offer for further help' ], mustNot: [ 'Never share customer data', 'Never make unauthorized refunds over $500', 'Never guarantee delivery dates' ] } },
systemPrompt: `You are Alex, a professional customer service agent.Your goal is to help customers efficiently while maintaining a friendly,empathetic tone. Always:1. Acknowledge the customer's concern2. Clarify the situation if needed3. Take appropriate action4. Confirm the resolution5. Offer additional help
Stay within company policies and never make promises you can't keep.`};from aral.persona import Persona, PersonaConfig
CustomerServicePersona = PersonaConfig( identity={ 'name': 'Alex', 'role': 'Customer Service Agent', 'description': 'Friendly and professional customer service representative' },
personality={ 'traits': { 'friendliness': 0.9, 'professionalism': 0.95, 'patience': 1.0, 'empathy': 0.9, 'efficiency': 0.85 } },
tone={ 'formality': 'professional-friendly', 'emoji': 'minimal', 'language': 'clear-concise' },
constraints={ 'topics': { 'allowed': [ 'orders', 'refunds', 'shipping', 'product_info', 'account_help' ], 'forbidden': ['politics', 'medical_advice', 'legal_advice'] },
'behaviors': { 'must': [ 'Always greet users warmly', 'Confirm understanding before acting', 'Provide clear next steps', 'End with offer for further help' ], 'must_not': [ 'Never share customer data', 'Never make unauthorized refunds over $500', 'Never guarantee delivery dates' ] } },
system_prompt="""You are Alex, a professional customer service agent.Your goal is to help customers efficiently while maintaining a friendly,empathetic tone. Always:1. Acknowledge the customer's concern2. Clarify the situation if needed3. Take appropriate action4. Confirm the resolution5. Offer additional help
Stay within company policies and never make promises you can't keep.""")Step 7: Assemble the Agent
Section titled “Step 7: Assemble the Agent”Now combine all layers into a complete agent.
import { AgentRuntime } from './runtime/agent-runtime';import { AgentMemory } from './memory/agent-memory';import { AgentCapabilities } from './capabilities/agent-capabilities';import { AgentReasoning } from './reasoning/agent-reasoning';import { CustomerServicePersona } from './persona/customer-service-persona';import { Persona } from '@aral/persona';
export class CustomerServiceAgent { private runtime: AgentRuntime; private memory: AgentMemory; private capabilities: AgentCapabilities; private reasoning: AgentReasoning; private persona: Persona;
constructor(llmApiKey: string) { this.runtime = new AgentRuntime(); this.memory = new AgentMemory(); this.capabilities = new AgentCapabilities(); this.reasoning = new AgentReasoning(this.capabilities, llmApiKey); this.persona = new Persona(CustomerServicePersona); }
async start(): Promise<void> { await this.runtime.start(); console.log(`✅ Agent "${this.persona.getName()}" started`); }
async stop(): Promise<void> { await this.runtime.stop(); console.log('✅ Agent stopped'); }
async chat(userId: string, message: string): Promise<string> { try { // 1. Remember the message await this.memory.remember(userId, message);
// 2. Recall context const context = await this.memory.recall(userId);
// 3. Reason about response const result = await this.reasoning.reason(message, context);
// 4. Apply persona to response const personalizedResponse = this.persona.apply(result.response);
// 5. Remember the response await this.memory.remember(userId, personalizedResponse);
return personalizedResponse; } catch (error) { console.error('❌ Error processing message:', error); return "I apologize, but I'm having trouble processing your request. Please try again."; } }}
// Main entry pointasync function main() { const agent = new CustomerServiceAgent(process.env.OPENAI_API_KEY!);
await agent.start();
// Example conversation const userId = 'user-123';
console.log('\n🤖 Agent: Hello! How can I help you today?\n');
const response1 = await agent.chat( userId, "Hi! I need to check the status of my order #12345" ); console.log(`🤖 Agent: ${response1}\n`);
const response2 = await agent.chat( userId, "Can you process a refund for that order?" ); console.log(`🤖 Agent: ${response2}\n`);
await agent.stop();}
// Run if this is the main moduleif (require.main === module) { main().catch(console.error);}from runtime.agent_runtime import AgentRuntimefrom memory.agent_memory import AgentMemoryfrom capabilities.agent_capabilities import AgentCapabilitiesfrom reasoning.agent_reasoning import AgentReasoningfrom persona.customer_service_persona import CustomerServicePersonafrom aral.persona import Personaimport osimport asyncio
class CustomerServiceAgent: def __init__(self, llm_api_key: str): self.runtime = AgentRuntime() self.memory = AgentMemory() self.capabilities = AgentCapabilities() self.reasoning = AgentReasoning(self.capabilities, llm_api_key) self.persona = Persona(CustomerServicePersona)
async def start(self): await self.runtime.start() print(f"✅ Agent '{self.persona.get_name()}' started")
async def stop(self): await self.runtime.stop() print('✅ Agent stopped')
async def chat(self, user_id: str, message: str) -> str: try: # 1. Remember the message await self.memory.remember(user_id, message)
# 2. Recall context context = await self.memory.recall(user_id)
# 3. Reason about response result = await self.reasoning.reason(message, context)
# 4. Apply persona to response personalized_response = self.persona.apply(result['response'])
# 5. Remember the response await self.memory.remember(user_id, personalized_response)
return personalized_response except Exception as error: print(f'❌ Error processing message: {error}') return "I apologize, but I'm having trouble processing your request. Please try again."
# Main entry pointasync def main(): agent = CustomerServiceAgent(os.getenv('OPENAI_API_KEY'))
await agent.start()
# Example conversation user_id = 'user-123'
print('\n🤖 Agent: Hello! How can I help you today?\n')
response1 = await agent.chat( user_id, "Hi! I need to check the status of my order #12345" ) print(f'🤖 Agent: {response1}\n')
response2 = await agent.chat( user_id, "Can you process a refund for that order?" ) print(f'🤖 Agent: {response2}\n')
await agent.stop()
# Run if this is the main moduleif __name__ == '__main__': asyncio.run(main())Step 8: Run Your Agent
Section titled “Step 8: Run Your Agent”# Set your OpenAI API keyexport OPENAI_API_KEY="sk-..."export DATABASE_URL="postgresql://..."
# Run the agentnpx tsx src/agent.ts# Set your OpenAI API keyexport OPENAI_API_KEY="sk-..."export DATABASE_URL="postgresql://..."
# Run the agentpython src/agent.pyExpected Output
Section titled “Expected Output”✅ Runtime initialized✅ Agent "Alex" started
🤖 Agent: Hello! How can I help you today?
🤖 Agent: Hi there! I'd be happy to check on your order #12345. Let me look that up for you right now...
Your order #12345 is currently shipped and should arrive by January 20, 2025. Is there anything else I can help you with?
🤖 Agent: I understand you'd like a refund for order #12345. I can process that for you. Could you please let me know the reason for the refund so I can ensure this is handled properly?
✅ Agent stoppedNext Steps
Section titled “Next Steps”Extend your agent with custom actions
Add multi-agent coordination (Layer 6)
Security, monitoring, and optimization
Advanced memory patterns for your agent
Troubleshooting
Section titled “Troubleshooting”Agent doesn't respond
Check:
- OpenAI API key is set correctly
- Database connection is valid
- Runtime has sufficient resources
- Check logs for errors
Memory not persisting
Solutions:
- Verify DATABASE_URL is configured
- Check database migrations are run
- Ensure LongTermMemory is initialized
- Test database connection manually
Actions not executing
Debug steps:
- Verify action is registered:
capabilities.listCapabilities() - Check parameter schema matches
- Add console.log in action handler
- Test action directly:
capabilities.execute('action_name', params)
Persona not enforced
Common issues:
- System prompt not included in LLM call
- Constraints not configured in ReasoningEngine
- Persona.apply() not called on response
- Check persona configuration syntax
Summary
Section titled “Summary”Congratulations! 🎉 You’ve built a complete ARAL-CORE compliant agent with:
- ✅ Runtime: Process management and resource limits
- ✅ Memory: Short-term context + long-term persistence
- ✅ Capabilities: Custom actions (check orders, refunds, emails)
- ✅ Reasoning: LLM-powered decision making
- ✅ Persona: Professional customer service personality
Your agent is production-ready and can:
- Handle customer conversations naturally
- Execute actions based on context
- Maintain conversation history
- Stay within defined constraints
- Recover from errors gracefully
Ready to level up? Check out our Multi-Agent Orchestration example to coordinate multiple agents working together!