Simple ARAL Agent Example
This is a complete, runnable example of a minimal ARAL-CORE agent. It demonstrates all five foundational layers in under 300 lines of code.
Information
Copy and run this example to see ARAL in action. Requires Node.js 18+ or Python 3.10+.
Complete Agent Example
Section titled “Complete Agent Example”Installation
Section titled “Installation”npm init -ynpm install @aral/sdk openainpm install -D typescript @types/node tsxComplete Code
Section titled “Complete Code”import { EventEmitter } from 'events';import OpenAI from 'openai';
// ==========================================// LAYER 1: RUNTIME// ==========================================class SimpleRuntime extends EventEmitter { private isRunning = false;
async start(): Promise<void> { this.isRunning = true; console.log('✅ Runtime started'); this.emit('started'); }
async stop(): Promise<void> { this.isRunning = false; console.log('✅ Runtime stopped'); this.emit('stopped'); }
healthCheck(): { status: string; uptime: number } { return { status: this.isRunning ? 'healthy' : 'stopped', uptime: process.uptime() }; }
async executeWithRetry<T>( fn: () => Promise<T>, maxRetries: number = 3 ): Promise<T> { let lastError: Error;
for (let attempt = 0; attempt <= maxRetries; attempt++) { try { return await fn(); } catch (error) { lastError = error as Error; if (attempt < maxRetries) { const delay = Math.min(1000 * Math.pow(2, attempt), 10000); await new Promise(resolve => setTimeout(resolve, delay)); } } }
throw lastError!; }}
// ==========================================// LAYER 2: MEMORY// ==========================================interface MemoryEntry { role: 'user' | 'assistant'; content: string; timestamp: number;}
class SimpleMemory { private messages: Map<string, MemoryEntry[]> = new Map(); private maxMessages = 20;
async remember(userId: string, role: 'user' | 'assistant', content: string): Promise<void> { if (!this.messages.has(userId)) { this.messages.set(userId, []); }
const userMessages = this.messages.get(userId)!; userMessages.push({ role, content, timestamp: Date.now() });
// Prune old messages (FIFO strategy) if (userMessages.length > this.maxMessages) { userMessages.shift(); } }
async recall(userId: string): Promise<MemoryEntry[]> { return this.messages.get(userId) || []; }
async clear(userId: string): Promise<void> { this.messages.delete(userId); }}
// ==========================================// LAYER 3: CAPABILITIES// ==========================================interface Action { name: string; description: string; parameters: any; handler: (params: any) => Promise<any>;}
class SimpleCapabilities { private actions: Map<string, Action> = new Map();
constructor() { this.registerBuiltInActions(); }
private registerBuiltInActions(): void { // Get current time this.register({ name: 'get_time', description: 'Get the current time', parameters: { type: 'object', properties: {} }, handler: async () => { return { time: new Date().toLocaleTimeString(), date: new Date().toLocaleDateString(), timestamp: Date.now() }; } });
// Calculate this.register({ name: 'calculate', description: 'Perform a mathematical calculation', parameters: { type: 'object', properties: { expression: { type: 'string', description: 'Math expression to evaluate' } }, required: ['expression'] }, handler: async (params) => { try { // Simple eval for demo (use a proper math parser in production!) const result = eval(params.expression); return { result, expression: params.expression }; } catch (error) { return { error: 'Invalid expression' }; } } });
// Search (mock) this.register({ name: 'web_search', description: 'Search the web for information', parameters: { type: 'object', properties: { query: { type: 'string', description: 'Search query' } }, required: ['query'] }, handler: async (params) => { // Mock response return { query: params.query, results: [ { title: 'Mock Result 1', url: 'https://example.com/1' }, { title: 'Mock Result 2', url: 'https://example.com/2' } ] }; } }); }
register(action: Action): void { this.actions.set(action.name, action); }
async execute(name: string, params: any): Promise<any> { const action = this.actions.get(name); if (!action) { throw new Error(`Action "${name}" not found`); }
// Validate parameters (simplified) if (action.parameters.required) { for (const required of action.parameters.required) { if (!(required in params)) { throw new Error(`Missing required parameter: ${required}`); } } }
return await action.handler(params); }
listActions(): string[] { return Array.from(this.actions.keys()); }
getActionDescription(name: string): string { const action = this.actions.get(name); return action ? action.description : ''; }}
// ==========================================// LAYER 4: REASONING// ==========================================class SimpleReasoning { private openai: OpenAI;
constructor( private capabilities: SimpleCapabilities, apiKey: string ) { this.openai = new OpenAI({ apiKey }); }
async reason( userMessage: string, context: MemoryEntry[] ): Promise<{ response: string; actions: any[] }> { // Build messages for OpenAI const messages: any[] = [ { role: 'system', content: this.buildSystemPrompt() }, ...context.map(entry => ({ role: entry.role, content: entry.content })), { role: 'user', content: userMessage } ];
// Call OpenAI with function calling const response = await this.openai.chat.completions.create({ model: 'gpt-4', messages, functions: this.getFunctionDefinitions(), function_call: 'auto', temperature: 0.7 });
const message = response.choices[0].message; const actions: any[] = [];
// Execute function if called if (message.function_call) { const functionName = message.function_call.name; const functionArgs = JSON.parse(message.function_call.arguments);
const result = await this.capabilities.execute(functionName, functionArgs); actions.push({ name: functionName, params: functionArgs, result });
// Get final response with function result const finalResponse = await this.openai.chat.completions.create({ model: 'gpt-4', messages: [ ...messages, { role: 'function', name: functionName, content: JSON.stringify(result) } ], temperature: 0.7 });
return { response: finalResponse.choices[0].message.content || 'No response', actions }; }
return { response: message.content || 'No response', actions }; }
private buildSystemPrompt(): string { return `You are a helpful AI assistant. You have access to the following capabilities:${this.capabilities.listActions().map(name =>`- ${name}: ${this.capabilities.getActionDescription(name)}`).join('\n')}
Use these capabilities when appropriate to help the user.`; }
private getFunctionDefinitions(): any[] { return [ { name: 'get_time', description: 'Get the current time and date', parameters: { type: 'object', properties: {} } }, { name: 'calculate', description: 'Perform a mathematical calculation', parameters: { type: 'object', properties: { expression: { type: 'string', description: 'Math expression like "2 + 2" or "Math.sqrt(16)"' } }, required: ['expression'] } }, { name: 'web_search', description: 'Search the web for information', parameters: { type: 'object', properties: { query: { type: 'string', description: 'Search query' } }, required: ['query'] } } ]; }}
// ==========================================// LAYER 5: PERSONA// ==========================================interface PersonaConfig { name: string; role: string; traits: { friendliness: number; professionalism: number; enthusiasm: number; }; tone: string;}
class SimplePersona { constructor(private config: PersonaConfig) {}
getName(): string { return this.config.name; }
getRole(): string { return this.config.role; }
apply(response: string): string { // Apply tone adjustments based on traits let adjusted = response;
if (this.config.traits.enthusiasm > 0.8) { // Add enthusiasm markers adjusted = adjusted.replace(/\./g, '!'); }
if (this.config.traits.friendliness > 0.8) { // Ensure friendly greeting if missing if (!adjusted.match(/^(Hi|Hello|Hey)/i)) { adjusted = `Hi there! ${adjusted}`; } }
return adjusted; }
validate(response: string): boolean { // Check constraints (simplified) const forbidden = ['politics', 'religion', 'violence']; const lowerResponse = response.toLowerCase();
for (const word of forbidden) { if (lowerResponse.includes(word)) { return false; } }
return true; }}
// ==========================================// MAIN AGENT// ==========================================class SimpleAgent { private runtime: SimpleRuntime; private memory: SimpleMemory; private capabilities: SimpleCapabilities; private reasoning: SimpleReasoning; private persona: SimplePersona;
constructor(config: { openaiApiKey: string; persona: PersonaConfig }) { this.runtime = new SimpleRuntime(); this.memory = new SimpleMemory(); this.capabilities = new SimpleCapabilities(); this.reasoning = new SimpleReasoning(this.capabilities, config.openaiApiKey); this.persona = new SimplePersona(config.persona); }
async start(): Promise<void> { await this.runtime.start(); console.log(`🤖 ${this.persona.getName()} (${this.persona.getRole()}) is ready!`); }
async stop(): Promise<void> { await this.runtime.stop(); }
async chat(userId: string, message: string): Promise<string> { try { // 1. Remember user message await this.memory.remember(userId, 'user', message);
// 2. Recall context const context = await this.memory.recall(userId);
// 3. Reason about response const result = await this.runtime.executeWithRetry(async () => { return await this.reasoning.reason(message, context); });
// 4. Apply persona let response = this.persona.apply(result.response);
// 5. Validate response if (!this.persona.validate(response)) { response = "I'm sorry, but I can't provide that kind of information."; }
// 6. Remember assistant response await this.memory.remember(userId, 'assistant', response);
// 7. Log actions if any if (result.actions.length > 0) { console.log('🔧 Actions executed:', result.actions); }
return response; } catch (error) { console.error('❌ Error:', error); return "I'm sorry, I encountered an error. Please try again."; } }
healthCheck() { return this.runtime.healthCheck(); }}
// ==========================================// USAGE EXAMPLE// ==========================================async function main() { // Create agent const agent = new SimpleAgent({ openaiApiKey: process.env.OPENAI_API_KEY!, persona: { name: 'Aria', role: 'Helpful Assistant', traits: { friendliness: 0.9, professionalism: 0.8, enthusiasm: 0.7 }, tone: 'friendly-professional' } });
// Start agent await agent.start();
const userId = 'demo-user';
// Example conversations console.log('\n--- Conversation 1: Basic chat ---'); const response1 = await agent.chat(userId, 'Hello! How are you?'); console.log(`🤖 ${response1}\n`);
console.log('--- Conversation 2: Use capability ---'); const response2 = await agent.chat(userId, 'What time is it?'); console.log(`🤖 ${response2}\n`);
console.log('--- Conversation 3: Calculate ---'); const response3 = await agent.chat(userId, 'Can you calculate 15 * 23 for me?'); console.log(`🤖 ${response3}\n`);
console.log('--- Conversation 4: Search ---'); const response4 = await agent.chat(userId, 'Search for information about ARAL'); console.log(`🤖 ${response4}\n`);
// Health check console.log('--- Health Check ---'); console.log(agent.healthCheck());
// Stop agent await agent.stop();}
// Runmain().catch(console.error);Run the Example
Section titled “Run the Example”export OPENAI_API_KEY="sk-..."npx tsx simple-agent.tsInstallation
Section titled “Installation”pip install openai asyncioComplete Code
Section titled “Complete Code”import osimport asyncioimport timefrom typing import List, Dict, Any, Optionalfrom openai import OpenAI
# ==========================================# LAYER 1: RUNTIME# ==========================================class SimpleRuntime: def __init__(self): self.is_running = False
async def start(self): self.is_running = True print('✅ Runtime started')
async def stop(self): self.is_running = False print('✅ Runtime stopped')
def health_check(self) -> dict: return { 'status': 'healthy' if self.is_running else 'stopped', 'uptime': time.time() }
async def execute_with_retry(self, fn, max_retries: int = 3): last_error = None
for attempt in range(max_retries + 1): try: return await fn() except Exception as error: last_error = error if attempt < max_retries: delay = min(1.0 * (2 ** attempt), 10.0) await asyncio.sleep(delay)
raise last_error
# ==========================================# LAYER 2: MEMORY# ==========================================class SimpleMemory: def __init__(self): self.messages: Dict[str, List[dict]] = {} self.max_messages = 20
async def remember(self, user_id: str, role: str, content: str): if user_id not in self.messages: self.messages[user_id] = []
self.messages[user_id].append({ 'role': role, 'content': content, 'timestamp': time.time() })
# Prune old messages if len(self.messages[user_id]) > self.max_messages: self.messages[user_id].pop(0)
async def recall(self, user_id: str) -> List[dict]: return self.messages.get(user_id, [])
async def clear(self, user_id: str): if user_id in self.messages: del self.messages[user_id]
# ==========================================# LAYER 3: CAPABILITIES# ==========================================class SimpleCapabilities: def __init__(self): self.actions = {} self._register_built_in_actions()
def _register_built_in_actions(self): # Get time self.register({ 'name': 'get_time', 'description': 'Get the current time', 'parameters': {'type': 'object', 'properties': {}}, 'handler': self._get_time })
# Calculate self.register({ 'name': 'calculate', 'description': 'Perform a mathematical calculation', 'parameters': { 'type': 'object', 'properties': { 'expression': {'type': 'string'} }, 'required': ['expression'] }, 'handler': self._calculate })
# Web search (mock) self.register({ 'name': 'web_search', 'description': 'Search the web for information', 'parameters': { 'type': 'object', 'properties': { 'query': {'type': 'string'} }, 'required': ['query'] }, 'handler': self._web_search })
async def _get_time(self, params): from datetime import datetime now = datetime.now() return { 'time': now.strftime('%H:%M:%S'), 'date': now.strftime('%Y-%m-%d'), 'timestamp': time.time() }
async def _calculate(self, params): try: result = eval(params['expression']) return {'result': result, 'expression': params['expression']} except Exception: return {'error': 'Invalid expression'}
async def _web_search(self, params): return { 'query': params['query'], 'results': [ {'title': 'Mock Result 1', 'url': 'https://example.com/1'}, {'title': 'Mock Result 2', 'url': 'https://example.com/2'} ] }
def register(self, action: dict): self.actions[action['name']] = action
async def execute(self, name: str, params: dict): if name not in self.actions: raise ValueError(f"Action '{name}' not found")
action = self.actions[name]
# Validate required parameters if 'required' in action['parameters']: for required in action['parameters']['required']: if required not in params: raise ValueError(f"Missing required parameter: {required}")
return await action['handler'](params)
def list_actions(self) -> List[str]: return list(self.actions.keys())
def get_action_description(self, name: str) -> str: return self.actions[name]['description'] if name in self.actions else ''
# ==========================================# LAYER 4: REASONING# ==========================================class SimpleReasoning: def __init__(self, capabilities: SimpleCapabilities, api_key: str): self.capabilities = capabilities self.openai = OpenAI(api_key=api_key)
async def reason(self, user_message: str, context: List[dict]) -> dict: messages = [ {'role': 'system', 'content': self._build_system_prompt()}, *[{'role': entry['role'], 'content': entry['content']} for entry in context], {'role': 'user', 'content': user_message} ]
# Call OpenAI response = self.openai.chat.completions.create( model='gpt-4', messages=messages, functions=self._get_function_definitions(), function_call='auto', temperature=0.7 )
message = response.choices[0].message actions = []
# Execute function if called if message.function_call: function_name = message.function_call.name import json function_args = json.loads(message.function_call.arguments)
result = await self.capabilities.execute(function_name, function_args) actions.append({'name': function_name, 'params': function_args, 'result': result})
# Get final response final_response = self.openai.chat.completions.create( model='gpt-4', messages=[ *messages, { 'role': 'function', 'name': function_name, 'content': json.dumps(result) } ], temperature=0.7 )
return { 'response': final_response.choices[0].message.content or 'No response', 'actions': actions }
return { 'response': message.content or 'No response', 'actions': actions }
def _build_system_prompt(self) -> str: capabilities = '\n'.join( f"- {name}: {self.capabilities.get_action_description(name)}" for name in self.capabilities.list_actions() ) return f"""You are a helpful AI assistant. You have access to these capabilities:{capabilities}
Use these capabilities when appropriate to help the user."""
def _get_function_definitions(self) -> List[dict]: return [ { 'name': 'get_time', 'description': 'Get the current time and date', 'parameters': {'type': 'object', 'properties': {}} }, { 'name': 'calculate', 'description': 'Perform a mathematical calculation', 'parameters': { 'type': 'object', 'properties': { 'expression': {'type': 'string'} }, 'required': ['expression'] } }, { 'name': 'web_search', 'description': 'Search the web', 'parameters': { 'type': 'object', 'properties': { 'query': {'type': 'string'} }, 'required': ['query'] } } ]
# ==========================================# LAYER 5: PERSONA# ==========================================class SimplePersona: def __init__(self, config: dict): self.config = config
def get_name(self) -> str: return self.config['name']
def get_role(self) -> str: return self.config['role']
def apply(self, response: str) -> str: adjusted = response
if self.config['traits']['enthusiasm'] > 0.8: adjusted = adjusted.replace('.', '!')
if self.config['traits']['friendliness'] > 0.8: if not adjusted.startswith(('Hi', 'Hello', 'Hey')): adjusted = f"Hi there! {adjusted}"
return adjusted
def validate(self, response: str) -> bool: forbidden = ['politics', 'religion', 'violence'] lower_response = response.lower()
for word in forbidden: if word in lower_response: return False
return True
# ==========================================# MAIN AGENT# ==========================================class SimpleAgent: def __init__(self, config: dict): self.runtime = SimpleRuntime() self.memory = SimpleMemory() self.capabilities = SimpleCapabilities() self.reasoning = SimpleReasoning(self.capabilities, config['openai_api_key']) self.persona = SimplePersona(config['persona'])
async def start(self): await self.runtime.start() print(f"🤖 {self.persona.get_name()} ({self.persona.get_role()}) is ready!")
async def stop(self): await self.runtime.stop()
async def chat(self, user_id: str, message: str) -> str: try: # 1. Remember await self.memory.remember(user_id, 'user', message)
# 2. Recall context = await self.memory.recall(user_id)
# 3. Reason async def reason_fn(): return await self.reasoning.reason(message, context)
result = await self.runtime.execute_with_retry(reason_fn)
# 4. Apply persona response = self.persona.apply(result['response'])
# 5. Validate if not self.persona.validate(response): response = "I'm sorry, but I can't provide that kind of information."
# 6. Remember await self.memory.remember(user_id, 'assistant', response)
# 7. Log actions if result['actions']: print('🔧 Actions executed:', result['actions'])
return response except Exception as error: print(f'❌ Error: {error}') return "I'm sorry, I encountered an error. Please try again."
def health_check(self): return self.runtime.health_check()
# ==========================================# USAGE EXAMPLE# ==========================================async def main(): agent = SimpleAgent({ 'openai_api_key': os.getenv('OPENAI_API_KEY'), 'persona': { 'name': 'Aria', 'role': 'Helpful Assistant', 'traits': { 'friendliness': 0.9, 'professionalism': 0.8, 'enthusiasm': 0.7 }, 'tone': 'friendly-professional' } })
await agent.start()
user_id = 'demo-user'
print('\n--- Conversation 1 ---') response1 = await agent.chat(user_id, 'Hello! How are you?') print(f'🤖 {response1}\n')
print('--- Conversation 2 ---') response2 = await agent.chat(user_id, 'What time is it?') print(f'🤖 {response2}\n')
print('--- Conversation 3 ---') response3 = await agent.chat(user_id, 'Calculate 15 * 23') print(f'🤖 {response3}\n')
print('--- Health Check ---') print(agent.health_check())
await agent.stop()
if __name__ == '__main__': asyncio.run(main())Run the Example
Section titled “Run the Example”export OPENAI_API_KEY="sk-..."python simple_agent.pyExpected Output
Section titled “Expected Output”✅ Runtime started🤖 Aria (Helpful Assistant) is ready!
--- Conversation 1: Basic chat ---🤖 Hi there! I'm doing great! Thanks for asking! How can I help you today?
--- Conversation 2: Use capability ---🔧 Actions executed: [{'name': 'get_time', 'params': {}, 'result': {'time': '14:30:15', 'date': '2025-01-15', 'timestamp': 1736952615}}]🤖 Hi there! It's currently 14:30:15 on 2025-01-15!
--- Conversation 3: Calculate ---🔧 Actions executed: [{'name': 'calculate', 'params': {'expression': '15 * 23'}, 'result': {'result': 345, 'expression': '15 * 23'}}]🤖 Hi there! 15 multiplied by 23 equals 345!
--- Conversation 4: Search ---🔧 Actions executed: [{'name': 'web_search', 'params': {'query': 'ARAL'}, 'result': {...}}]🤖 Hi there! I found some information about ARAL for you!
--- Health Check ---{'status': 'healthy', 'uptime': 12.5}
✅ Runtime stoppedKey Concepts Demonstrated
Section titled “Key Concepts Demonstrated”Layer Separation
Each layer is independent:
- Runtime handles execution lifecycle
- Memory manages conversation state
- Capabilities defines available actions
- Reasoning makes decisions
- Persona enforces behavior
This modularity makes testing and extending easy.
Error Handling
The runtime provides automatic retry with exponential backoff:
await runtime.executeWithRetry(async () => { return await reasoning.reason(message, context);});This makes the agent resilient to transient LLM API failures.
Function Calling
The agent uses OpenAI function calling to:
- Present available capabilities to the LLM
- Extract structured action requests
- Execute capabilities automatically
- Return results to the LLM for final response
This creates seamless tool use.
Persona Enforcement
The persona layer:
- Adjusts response tone based on traits
- Validates responses against constraints
- Ensures consistent character behavior
This maintains agent identity across conversations.
Extending the Example
Section titled “Extending the Example”➕ Add Custom Capabilities
Register new actions:
capabilities.register({ name: 'send_email', description: 'Send an email', parameters: { ... }, handler: async (params) => { ... }});🗄️ Persistent Memory
Replace in-memory storage with a database:
class PersistentMemory extends SimpleMemory { async remember(...) { await db.insert(...); }}👥 Multiple Personas
Switch personas dynamically:
agent.setPersona(TechnicalExpertPersona);🧠 Advanced Reasoning
Add chain-of-thought:
reasoning.enableChainOfThought();