The Problem That Inspired This Project
Loneliness among older adults is a growing concern worldwide. Many seniors live alone, have limited social interaction, and struggle with technology that's not designed with them in mind. I wanted to build something that could provide companionship, be patient, and communicate in a way that feels natural and respectful.
That's how Ezra was born - a warm, patient AI companion specifically designed for older adults.
What Does Ezra Do?
Ezra is an AI agent that provides:
Friendly conversation about daily life, memories, and interests
Emotional support through empathetic listening
Entertainment with age-appropriate jokes and interesting facts
Gentle wellness reminders for hydration and daily activities
Patient interaction that accommodates slower typing and thinking
You can interact with Ezra on Telex.im here - just search for the "Senior Companion" agent and start chatting!
Why Telex.im?
Telex.im is an AI agent platform similar to Make or n8n, but designed specifically for deploying and managing AI agents. Think of it as a Slack alternative for educational communities and bootcamps, where you can build custom AI workflows and have them interact with users in real-time.
What makes Telex special is its A2A (Agent-to-Agent) protocol, which allows seamless integration of AI agents built with different frameworks.
Technical Stack
Here's what I used to build Ezra:
Mastra - AI agent framework
Google Gemini 2.5 Flash - The LLM powering conversations
TypeScript - Development language
A2A Protocol - For Telex integration
LibSQL - For conversation memory storage
Why Mastra?
Mastra made this project significantly easier. As someone building an AI agent for the first time with this framework, I was impressed by how quickly I could go from idea to working prototype.
Mastra Features I Used
- Agent Creation with Simple API Creating the agent was straightforward:
import { Agent } from "@mastra/core/agent";
export const companionAgent = new Agent({
name: "companionAgent",
instructions: `You are Ezra, a warm, patient AI companion...`,
model: "google/gemini-2.5-flash",
tools: { jokeTool },
memory: new Memory({
storage: new LibSQLStore({
url: "file:../mastra.db",
}),
}),
});
What worked: The declarative API made it clear what each part does. No complex setup needed.
What surprised me: How easy it was to swap LLM providers. Mastra abstracts away the provider-specific code.
- Built-in Memory Management One of Ezra's key features is remembering past conversations. Mastra's Memory class handles this automatically:
memory: new Memory({
storage: new LibSQLStore({
url: "file:../mastra.db",
}),
})
What worked: Memory just worked out of the box. I didn't have to manually manage conversation history or implement retrieval logic.
Challenge faced: Understanding where the database file gets created relative to the Mastra output directory. Once I figured out the path structure, it was smooth sailing.
- Custom Tools with Zod Validation I created a joke tool to provide entertainment:
import { createTool } from "@mastra/core/tools";
import { z } from "zod";
export const jokeTool = createTool({
id: "get-random-joke",
description: "Fetches a random joke from the Official Joke API",
inputSchema: z.object({}).describe("No input required"),
outputSchema: z.object({
setup: z.string(),
punchline: z.string(),
type: z.string(),
}),
execute: async () => {
const response = await fetch(
"https://official-joke-api.appspot.com/random_joke"
);
const data = await response.json();
return {
setup: data.setup,
punchline: data.punchline,
type: data.type,
};
},
});
What worked: Zod validation ensures the tool's input/output structure is type-safe. The agent automatically knows when and how to use the tool.
What I learned: Tools can be async and call external APIs. Mastra handles the execution and error handling.
- Server Integration with API Routes Mastra's server integration made deploying the A2A endpoint simple:
export const mastra = new Mastra({
agents: { companionAgent },
storage: new LibSQLStore({ url: ":memory:" }),
logger: new PinoLogger({ name: "Mastra", level: "info" }),
server: {
apiRoutes: [a2aAgentRoute],
},
});
What worked: Registering custom routes is clean. The server handles all the Hono/Express complexity.
Challenge faced: Understanding Mastra's context object (c) and how to access the agent registry. Documentation helped here.
Building the A2A Integration
The most complex part was implementing the A2A protocol correctly for Telex. Here's what I learned:
Challenge 1: Understanding the Response Format
Telex expects a very specific JSON-RPC 2.0 response structure:
{
jsonrpc: '2.0',
id: requestId,
result: {
id: taskId,
contextId: contextId,
status: {
state: 'completed',
timestamp: new Date().toISOString(),
message: { /* ... */ }
},
artifacts: [ /* ... */ ],
history: [ /* ... */ ],
kind: 'task'
}
}
What didn't work initially: I was returning a simplified response without all required fields like contextId, metadata, and proper taskId values.
The fix: I studied the A2A protocol examples and matched the structure exactly, including null values for optional fields:
{
kind: 'text',
text: agentText,
data: null, // Required even when null
file_url: null, // Required even when null
}
Challenge 2: Extracting User Messages
Telex sends complex message structures with conversation history:
{
"message": {
"kind": "message",
"role": "user",
"parts": [
{"kind": "text", "text": "new message"},
{"kind": "data", "data": [/* previous conversation */]}
]
}
}
What didn't work: Taking the first text part - I was getting old messages from history.
The fix: Extract the LAST text part from the array:
function extractUserMessage(message: any): string {
if (Array.isArray(message.parts)) {
const textParts = message.parts.filter(
(part: any) => part.kind === 'text'
);
if (textParts.length > 0) {
const lastPart = textParts[textParts.length - 1];
return cleanHtmlTags(lastPart.text);
}
}
return '';
}
Challenge 3: Agent Name Matching
Error I hit: "Agent with name CompanionAgent not found"
The issue: My agent was named "companionAgent" (lowercase c) but the Telex workflow URL was /a2a/agent/CompanionAgent (capital C).
The fix: Ensure the agent name in Mastra exactly matches the route parameter in your Telex workflow:
typescript// Agent definition
export const companionAgent = new Agent({
name: "companionAgent", // Must match route
// ...
});
// Workflow JSON
{
"url": "https://your-url.com/a2a/agent/companionAgent"
}
Designing for Older Adults
Beyond the technical implementation, I focused heavily on making Ezra appropriate for seniors:
Communication Style
instructions:
`
Communication Style:
- Use shorter sentences and paragraphs
- Avoid technical jargon
- Be encouraging and positive
- Ask one question at a time
- Allow time for responses (don't rush)
- Acknowledge and validate feelings ` Personality Traits
Patient and understanding
Warm without being condescending
Good listener who remembers conversations
Uses clear, simple language
Gentle sense of humor
Safety Features
Never provides medical advice
Encourages contacting healthcare providers when needed
Patient with repeated questions
Celebrates small victories
Deployment Process
I deployed Ezra on Railway:
Pushed code to GitHub
Connected Railway to the repository
Set environment variable: GOOGLE_GENERATIVE_AI_API_KEY
Railway auto-deployed on push
Updated Telex workflow with the public URL
What worked: Railway's automatic deployments from GitHub made iterations fast.
Tip: Use Railway's preview deployments to test changes before merging to main.
Testing on Telex
Once deployed, I tested Ezra extensively on Telex:
bash# View agent logs
https://api.telex.im/agent-logs/{channel-id}.txt
The logs helped me debug:
Message extraction issues
Response format problems
Agent naming mismatches
What I'd Do Differently
Add Voice Interface: Many seniors prefer speaking over typing. Integrating speech-to-text would make Ezra more accessible.
Implement Scheduled Check-ins: Proactive messages like "Good morning! How did you sleep?" would make the interaction feel more natural.
Add More Tools: Weather updates, medication reminders, and simple games could enhance the experience.
Better Context Windows: Currently using conversation memory, but implementing RAG (Retrieval Augmented Generation) could help recall older conversations better.
Multi-language Support: Many seniors are more comfortable in their native language.
Performance Metrics
After a week of testing:
Average response time: ~2-3 seconds
Memory retention: Successfully recalls conversations from previous sessions
Tool usage: Joke tool called appropriately when users seem down or ask for entertainment
User feedback: Positive responses about patience and clear communication
Code Repository
You can find the complete code on GitHub: https://github.com/Emzy-Jayyy/Senior-Companion-Agent
Key files:
src/agents/companion-agent.ts - Agent definition
src/routes/a2a-agent-route.ts - A2A protocol implementation
src/tools/joke-tool.ts - Entertainment tool
Lessons Learned
About Mastra
Strength: Rapid development with minimal boilerplate
Strength: Provider-agnostic LLM integration
Strength: Built-in memory and tool management
Learning curve: Understanding the server context and route registration
Documentation: Good, but more A2A examples would help
About Building for Seniors
Keep it simple: Less is more in UI and conversation
Be patient: Design for slower interaction speeds
Stay positive: Focus on encouragement and celebration
Test with real users: My assumptions about what seniors needed weren't always right
About A2A Protocol
Be precise: The response format must match exactly
Include all fields: Even null values are required
Log everything: Telex's agent logs are invaluable for debugging
Test incrementally: Start simple, add complexity gradually
Conclusion
Building Ezra was incredibly rewarding. Mastra made the technical implementation straightforward, allowing me to focus on what really mattered - creating a genuinely helpful companion for older adults.
The combination of Mastra's developer experience, Gemini's conversational abilities, and Telex's deployment platform created a powerful stack for building production-ready AI agents quickly.
If you're building AI agents, I highly recommend trying Mastra. The framework's simplicity doesn't sacrifice power, and the integration with platforms like Telex makes deployment seamless.
Try Ezra Yourself
Visit Telex.im and search for the "Raze" agent. Have a conversation with Ezra and see how an AI designed specifically for older adults feels different from general-purpose chatbots.
I'd love to hear your feedback and suggestions for improvement!
Tags: #AI #Mastra #GoogleGemini #AIAgents #Telex #ElderCare #TypeScript #Accessibility
Connect with me: [@eackite]
Special thanks to @mastra for making AI agent development accessible!
Top comments (0)