Skip to content

Building Your First ARAL Agent

Information

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.

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

Terminal window
# Create new project
mkdir my-first-aral-agent
cd my-first-aral-agent
npm init -y
# Install ARAL SDK
npm install @aral/sdk @aral/runtime @aral/memory @aral/capabilities
# Install dependencies
npm install typescript @types/node tsx
npx tsc --init
Terminal window
# Create virtual environment
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
# Install ARAL SDK
pip install aral-sdk
# Create project structure
mkdir -p src/{runtime,memory,capabilities,reasoning,persona}
touch src/main.py

The Runtime Layer manages the execution lifecycle of your agent.

src/runtime/agent-runtime.ts
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;
}
}
src/runtime/agent_runtime.py
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.runtime

Memory enables your agent to maintain context across conversations.

src/memory/agent-memory.ts
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];
}
}
src/memory/agent_memory.py
from aral.memory import MemoryManager, ShortTermMemory, LongTermMemory
import os
from 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]

Capabilities are the actions your agent can perform.

src/capabilities/agent-capabilities.ts
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();
}
}
src/capabilities/agent_capabilities.py
from aral.capabilities import CapabilityRegistry, Action
from 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()

The Reasoning Layer handles decision-making and planning.

src/reasoning/agent-reasoning.ts
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.
`;
}
}
src/reasoning/agent_reasoning.py
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.
"""

The Persona Layer defines your agent’s identity and behavior.

src/persona/customer-service-persona.ts
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 concern
2. Clarify the situation if needed
3. Take appropriate action
4. Confirm the resolution
5. Offer additional help
Stay within company policies and never make promises you can't keep.`
};
src/persona/customer_service_persona.py
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 concern
2. Clarify the situation if needed
3. Take appropriate action
4. Confirm the resolution
5. Offer additional help
Stay within company policies and never make promises you can't keep."""
)

Now combine all layers into a complete agent.

src/agent.ts
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 point
async 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 module
if (require.main === module) {
main().catch(console.error);
}
src/agent.py
from runtime.agent_runtime import AgentRuntime
from memory.agent_memory import AgentMemory
from capabilities.agent_capabilities import AgentCapabilities
from reasoning.agent_reasoning import AgentReasoning
from persona.customer_service_persona import CustomerServicePersona
from aral.persona import Persona
import os
import 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 point
async 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 module
if __name__ == '__main__':
asyncio.run(main())

Terminal window
# Set your OpenAI API key
export OPENAI_API_KEY="sk-..."
export DATABASE_URL="postgresql://..."
# Run the agent
npx tsx src/agent.ts
Terminal window
# Set your OpenAI API key
export OPENAI_API_KEY="sk-..."
export DATABASE_URL="postgresql://..."
# Run the agent
python src/agent.py
✅ 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 stopped


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:

  1. Verify action is registered: capabilities.listCapabilities()
  2. Check parameter schema matches
  3. Add console.log in action handler
  4. 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

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
Information

Ready to level up? Check out our Multi-Agent Orchestration example to coordinate multiple agents working together!