<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Emzy-Jayyy</title>
    <description>The latest articles on Forem by Emzy-Jayyy (@emzyjayyy).</description>
    <link>https://forem.com/emzyjayyy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1791961%2Ff03fe509-2787-4a14-a0c0-2180d91bba07.png</url>
      <title>Forem: Emzy-Jayyy</title>
      <link>https://forem.com/emzyjayyy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/emzyjayyy"/>
    <language>en</language>
    <item>
      <title>Building Ezra: An AI Companion for Older Adults with Mastra and Gemini</title>
      <dc:creator>Emzy-Jayyy</dc:creator>
      <pubDate>Mon, 03 Nov 2025 14:10:06 +0000</pubDate>
      <link>https://forem.com/emzyjayyy/building-an-ai-companion-for-older-adults-using-mastra-and-telex-a2a-protocol-276n</link>
      <guid>https://forem.com/emzyjayyy/building-an-ai-companion-for-older-adults-using-mastra-and-telex-a2a-protocol-276n</guid>
      <description>&lt;p&gt;The Problem That Inspired This Project&lt;br&gt;
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.&lt;br&gt;
That's how Ezra was born - a warm, patient AI companion specifically designed for older adults.&lt;br&gt;
What Does Ezra Do?&lt;br&gt;
Ezra is an AI agent that provides:&lt;/p&gt;

&lt;p&gt;Friendly conversation about daily life, memories, and interests&lt;br&gt;
Emotional support through empathetic listening&lt;br&gt;
Entertainment with age-appropriate jokes and interesting facts&lt;br&gt;
Gentle wellness reminders for hydration and daily activities&lt;br&gt;
Patient interaction that accommodates slower typing and thinking&lt;/p&gt;

&lt;p&gt;You can interact with Ezra on Telex.im here - just search for the "Senior Companion" agent and start chatting!&lt;br&gt;
Why Telex.im?&lt;br&gt;
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.&lt;br&gt;
What makes Telex special is its A2A (Agent-to-Agent) protocol, which allows seamless integration of AI agents built with different frameworks.&lt;br&gt;
Technical Stack&lt;br&gt;
Here's what I used to build Ezra:&lt;/p&gt;

&lt;p&gt;Mastra - AI agent framework&lt;br&gt;
Google Gemini 2.5 Flash - The LLM powering conversations&lt;br&gt;
TypeScript - Development language&lt;br&gt;
A2A Protocol - For Telex integration&lt;br&gt;
LibSQL - For conversation memory storage&lt;/p&gt;

&lt;p&gt;Why Mastra?&lt;br&gt;
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.&lt;br&gt;
Mastra Features I Used&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Agent Creation with Simple API
Creating the agent was straightforward:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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",
    }),
  }),
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;What worked: The declarative API made it clear what each part does. No complex setup needed.&lt;br&gt;
What surprised me: How easy it was to swap LLM providers. Mastra abstracts away the provider-specific code.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Built-in Memory Management
One of Ezra's key features is remembering past conversations. Mastra's Memory class handles this automatically:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;memory: new Memory({
  storage: new LibSQLStore({
    url: "file:../mastra.db",
  }),
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;What worked: Memory just worked out of the box. I didn't have to manually manage conversation history or implement retrieval logic.&lt;br&gt;
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.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Custom Tools with Zod Validation
I created a joke tool to provide entertainment:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;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 () =&amp;gt; {
    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,
    };
  },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;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.&lt;br&gt;
What I learned: Tools can be async and call external APIs. Mastra handles the execution and error handling.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Server Integration with API Routes
Mastra's server integration made deploying the A2A endpoint simple:
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export const mastra = new Mastra({
  agents: { companionAgent },
  storage: new LibSQLStore({ url: ":memory:" }),
  logger: new PinoLogger({ name: "Mastra", level: "info" }),
  server: {
    apiRoutes: [a2aAgentRoute],
  },
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;What worked: Registering custom routes is clean. The server handles all the Hono/Express complexity.&lt;br&gt;
Challenge faced: Understanding Mastra's context object (c) and how to access the agent registry. Documentation helped here.&lt;br&gt;
Building the A2A Integration&lt;br&gt;
The most complex part was implementing the A2A protocol correctly for Telex. Here's what I learned:&lt;br&gt;
Challenge 1: Understanding the Response Format&lt;br&gt;
Telex expects a very specific JSON-RPC 2.0 response structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  jsonrpc: '2.0',
  id: requestId,
  result: {
    id: taskId,
    contextId: contextId,
    status: {
      state: 'completed',
      timestamp: new Date().toISOString(),
      message: { /* ... */ }
    },
    artifacts: [ /* ... */ ],
    history: [ /* ... */ ],
    kind: 'task'
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What didn't work initially: I was returning a simplified response without all required fields like contextId, metadata, and proper taskId values.&lt;br&gt;
The fix: I studied the A2A protocol examples and matched the structure exactly, including null values for optional fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  kind: 'text',
  text: agentText,
  data: null,      // Required even when null
  file_url: null,  // Required even when null
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Challenge 2: Extracting User Messages&lt;br&gt;
Telex sends complex message structures with conversation history:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "message": {
    "kind": "message",
    "role": "user",
    "parts": [
      {"kind": "text", "text": "new message"},
      {"kind": "data", "data": [/* previous conversation */]}
    ]
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;What didn't work: Taking the first text part - I was getting old messages from history.&lt;br&gt;
The fix: Extract the LAST text part from the array:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;function extractUserMessage(message: any): string {
  if (Array.isArray(message.parts)) {
    const textParts = message.parts.filter(
      (part: any) =&amp;gt; part.kind === 'text'
    );

    if (textParts.length &amp;gt; 0) {
      const lastPart = textParts[textParts.length - 1];
      return cleanHtmlTags(lastPart.text);
    }
  }
  return '';
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Challenge 3: Agent Name Matching&lt;br&gt;
Error I hit: "Agent with name CompanionAgent not found"&lt;br&gt;
The issue: My agent was named "companionAgent" (lowercase c) but the Telex workflow URL was /a2a/agent/CompanionAgent (capital C).&lt;br&gt;
The fix: Ensure the agent name in Mastra exactly matches the route parameter in your Telex workflow:&lt;br&gt;
typescript// Agent definition&lt;br&gt;
&lt;code&gt;export const companionAgent = new Agent({&lt;br&gt;
  name: "companionAgent",  // Must match route&lt;br&gt;
  // ...&lt;br&gt;
});&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;// Workflow JSON&lt;br&gt;
&lt;code&gt;{&lt;br&gt;
  "url": "https://your-url.com/a2a/agent/companionAgent"&lt;br&gt;
}&lt;/code&gt;&lt;br&gt;
Designing for Older Adults&lt;br&gt;
Beyond the technical implementation, I focused heavily on making Ezra appropriate for seniors:&lt;br&gt;
Communication Style&lt;br&gt;
instructions: &lt;br&gt;
`&lt;br&gt;
&lt;strong&gt;Communication Style:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Use shorter sentences and paragraphs&lt;/li&gt;
&lt;li&gt;Avoid technical jargon&lt;/li&gt;
&lt;li&gt;Be encouraging and positive&lt;/li&gt;
&lt;li&gt;Ask one question at a time&lt;/li&gt;
&lt;li&gt;Allow time for responses (don't rush)&lt;/li&gt;
&lt;li&gt;Acknowledge and validate feelings
`
Personality Traits&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Patient and understanding&lt;br&gt;
Warm without being condescending&lt;br&gt;
Good listener who remembers conversations&lt;br&gt;
Uses clear, simple language&lt;br&gt;
Gentle sense of humor&lt;/p&gt;

&lt;p&gt;Safety Features&lt;/p&gt;

&lt;p&gt;Never provides medical advice&lt;br&gt;
Encourages contacting healthcare providers when needed&lt;br&gt;
Patient with repeated questions&lt;br&gt;
Celebrates small victories&lt;/p&gt;

&lt;p&gt;Deployment Process&lt;br&gt;
I deployed Ezra on Railway:&lt;/p&gt;

&lt;p&gt;Pushed code to GitHub&lt;br&gt;
Connected Railway to the repository&lt;br&gt;
Set environment variable: GOOGLE_GENERATIVE_AI_API_KEY&lt;br&gt;
Railway auto-deployed on push&lt;br&gt;
Updated Telex workflow with the public URL&lt;/p&gt;

&lt;p&gt;What worked: Railway's automatic deployments from GitHub made iterations fast.&lt;br&gt;
Tip: Use Railway's preview deployments to test changes before merging to main.&lt;br&gt;
Testing on Telex&lt;br&gt;
Once deployed, I tested Ezra extensively on Telex:&lt;br&gt;
bash# View agent logs&lt;br&gt;
&lt;code&gt;https://api.telex.im/agent-logs/{channel-id}.txt&lt;/code&gt;&lt;br&gt;
The logs helped me debug:&lt;/p&gt;

&lt;p&gt;Message extraction issues&lt;br&gt;
Response format problems&lt;br&gt;
Agent naming mismatches&lt;/p&gt;

&lt;p&gt;What I'd Do Differently&lt;/p&gt;

&lt;p&gt;Add Voice Interface: Many seniors prefer speaking over typing. Integrating speech-to-text would make Ezra more accessible.&lt;br&gt;
Implement Scheduled Check-ins: Proactive messages like "Good morning! How did you sleep?" would make the interaction feel more natural.&lt;br&gt;
Add More Tools: Weather updates, medication reminders, and simple games could enhance the experience.&lt;br&gt;
Better Context Windows: Currently using conversation memory, but implementing RAG (Retrieval Augmented Generation) could help recall older conversations better.&lt;br&gt;
Multi-language Support: Many seniors are more comfortable in their native language.&lt;/p&gt;

&lt;p&gt;Performance Metrics&lt;br&gt;
After a week of testing:&lt;/p&gt;

&lt;p&gt;Average response time: ~2-3 seconds&lt;br&gt;
Memory retention: Successfully recalls conversations from previous sessions&lt;br&gt;
Tool usage: Joke tool called appropriately when users seem down or ask for entertainment&lt;br&gt;
User feedback: Positive responses about patience and clear communication&lt;/p&gt;

&lt;p&gt;Code Repository&lt;br&gt;
You can find the complete code on GitHub: &lt;a href="https://github.com/Emzy-Jayyy/Senior-Companion-Agent" rel="noopener noreferrer"&gt;https://github.com/Emzy-Jayyy/Senior-Companion-Agent&lt;/a&gt;&lt;br&gt;
Key files:&lt;/p&gt;

&lt;p&gt;src/agents/companion-agent.ts - Agent definition&lt;br&gt;
src/routes/a2a-agent-route.ts - A2A protocol implementation&lt;br&gt;
src/tools/joke-tool.ts - Entertainment tool&lt;/p&gt;

&lt;p&gt;Lessons Learned&lt;br&gt;
About Mastra&lt;/p&gt;

&lt;p&gt;Strength: Rapid development with minimal boilerplate&lt;br&gt;
Strength: Provider-agnostic LLM integration&lt;br&gt;
Strength: Built-in memory and tool management&lt;br&gt;
Learning curve: Understanding the server context and route registration&lt;br&gt;
Documentation: Good, but more A2A examples would help&lt;/p&gt;

&lt;p&gt;About Building for Seniors&lt;/p&gt;

&lt;p&gt;Keep it simple: Less is more in UI and conversation&lt;br&gt;
Be patient: Design for slower interaction speeds&lt;br&gt;
Stay positive: Focus on encouragement and celebration&lt;br&gt;
Test with real users: My assumptions about what seniors needed weren't always right&lt;/p&gt;

&lt;p&gt;About A2A Protocol&lt;/p&gt;

&lt;p&gt;Be precise: The response format must match exactly&lt;br&gt;
Include all fields: Even null values are required&lt;br&gt;
Log everything: Telex's agent logs are invaluable for debugging&lt;br&gt;
Test incrementally: Start simple, add complexity gradually&lt;/p&gt;

&lt;p&gt;Conclusion&lt;br&gt;
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.&lt;br&gt;
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.&lt;br&gt;
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.&lt;br&gt;
Try Ezra Yourself&lt;br&gt;
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.&lt;br&gt;
I'd love to hear your feedback and suggestions for improvement!&lt;/p&gt;

&lt;p&gt;Tags: #AI #Mastra #GoogleGemini #AIAgents #Telex #ElderCare #TypeScript #Accessibility&lt;br&gt;
Connect with me: [@eackite]&lt;br&gt;
Special thanks to @mastra for making AI agent development accessible!&lt;/p&gt;

</description>
      <category>ai</category>
      <category>typescript</category>
      <category>backend</category>
    </item>
  </channel>
</rss>
