Skip to content

Yebo Agent Framework

Personal AI agents for every African's economic life - at scale, cheaply.

Created: 2026-04-01
Author: Laslie Georges Jr. + AI Architecture Session


The Vision

Each Yebo user gets a personal AI agent that:

  • Knows them - learns from every interaction
  • Watches for them - triggers on relevant events
  • Acts for them - uses MCP tools on their behalf
  • Grows with them - compounds knowledge over time

Core Insight: Serverless Agents

Agents don't run constantly. They:

  • Sleep 99% of the time
  • Wake up on: user message, event trigger, cron schedule
  • Work for seconds, then sleep again

This makes it cheap to scale to millions of users.


Architecture Overview

┌────────────────────────────────────────────────────────────┐
│                    MCP PRODUCTS                             │
│  YeboShops | YeboJobs | YeboLink | YeboLearn | YeboSafe   │
└─────────────────────────┬──────────────────────────────────┘
                          ▼ (webhooks)
┌────────────────────────────────────────────────────────────┐
│                    EVENT BUS                                │
│               (Redis Streams or Pub/Sub)                    │
└─────────────────────────┬──────────────────────────────────┘

┌────────────────────────────────────────────────────────────┐
│              TRIGGER EVALUATOR                              │
│         (JSON rule matching, no AI)                         │
│                                                             │
│   Postgres: triggers table                                  │
│   For each event → find matching triggers → queue wake-ups │
└─────────────────────────┬──────────────────────────────────┘

┌────────────────────────────────────────────────────────────┐
│                  TASK QUEUE                                 │
│            (Cloud Tasks / BullMQ)                           │
└─────────────────────────┬──────────────────────────────────┘

┌────────────────────────────────────────────────────────────┐
│              AGENT RUNTIME (Hiyebo API)                     │
├────────────────────────────────────────────────────────────┤
│  1. Load yeboid-{userId}.md from R2                         │
│  2. Load trigger context                                    │
│  3. AI reasons, uses MCP tools                              │
│  4. Updates identity, creates/removes triggers              │
│  5. Sends message to Hiyebo app                             │
│  6. Sleeps                                                  │
└─────────────────────────┬──────────────────────────────────┘

┌────────────────────────────────────────────────────────────┐
│                HIYEBO APP (User's Interface)                │
│              SSE for real-time, FCM for push                │
└────────────────────────────────────────────────────────────┘

Per-User Storage (R2)

Bucket: hiyebo-agents

/agents/{userId}/
├── yeboid-{userId}.md      # WHO they are (identity + preferences)
├── memory.md               # WHAT happened (interaction history)
├── triggers.json           # WHAT they're watching for (cached)
└── context/
    └── session.json        # Current conversation state

Cost: ~$0.015/GB/month = pennies for millions of users


The Identity File: yeboid-{userId}.md

This is the canonical source of truth about each user:

markdown
# YeboID: {userId}

## Identity
- **Name:** Tendai Moyo
- **Phone:** +263...
- **Location:** Harare, Zimbabwe
- **Language:** English, Shona
- **Timezone:** Africa/Harare (UTC+2)
- **Verified:** ✓ Phone, ✓ ID

## Profile
- **Occupation:** Software Developer
- **Skills:** React, Node.js, Python
- **Experience:** 4 years
- **Education:** BSc Computer Science, UZ

## Preferences
- **Communication:** Concise, technical
- **Notifications:** Immediate for jobs, batched for listings
- **Currency:** USD preferred, ZWL accepted
- **Price Sensitivity:** Medium (values quality)

## Economic Activity

### Selling
- Active seller on YeboShops (12 listings, 8 sold)
- Categories: Electronics, Phones
- Avg response time: 2 hours
- Rating: 4.7/5

### Buying
- Purchased: iPhone 13 (E8,500), MacBook Air (E15,000)
- Interests: Tech gadgets, refurbished electronics
- Price alerts: iPhone 15 < E10,000

### Employment
- Status: Employed, open to offers
- Looking for: Senior React roles, Remote OK
- Salary expectation: $3,000-5,000/month
- Last application: 2026-03-15 (Safaricom)

### Learning
- Enrolled: AWS Cloud Practitioner (YeboLearn)
- Completed: React Advanced Patterns
- Interests: Cloud architecture, System design

## Behavior Patterns
- Most active: Evenings (18:00-22:00)
- Responds quickly to job matches
- Prefers images when selling
- Negotiates 10-15% on purchases

## Agent Notes
- Likes direct communication, no fluff
- Mentioned budget constraints in March
- Interested in relocating to South Africa
- Birthday: March 15 (wished him last year)

## Trust Indicators
- Member since: 2025-06-01
- Transactions: 23 successful
- Disputes: 0
- Verification level: Full

Triggers System

Database Schema

sql
CREATE TABLE triggers (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID NOT NULL REFERENCES users(id),
  
  -- What fires this trigger
  type TEXT NOT NULL,  -- 'event', 'cron', 'reminder'
  source TEXT,         -- 'yeboshops', 'yebojobs', etc.
  event TEXT,          -- 'listing.created', 'job.posted', etc.
  
  -- Conditions (JSONB for flexible matching)
  conditions JSONB DEFAULT '{}',
  
  -- When to fire (for cron/reminder)
  schedule TEXT,       -- Cron expression
  fire_at TIMESTAMPTZ, -- One-time reminder
  
  -- Context for agent when it wakes
  context JSONB DEFAULT '{}',
  
  -- State
  active BOOLEAN DEFAULT true,
  last_fired_at TIMESTAMPTZ,
  fire_count INT DEFAULT 0,
  
  created_at TIMESTAMPTZ DEFAULT now(),
  updated_at TIMESTAMPTZ DEFAULT now()
);

CREATE INDEX idx_triggers_event ON triggers(source, event) WHERE active = true;
CREATE INDEX idx_triggers_cron ON triggers(type) WHERE type = 'cron' AND active = true;
CREATE INDEX idx_triggers_reminder ON triggers(fire_at) WHERE type = 'reminder' AND active = true;

Trigger Types

1. Event Triggers - React to product events

json
{
  "type": "event",
  "source": "yebojobs",
  "event": "job.posted",
  "conditions": { "skills": ["react"], "location": "nairobi" },
  "context": { "reason": "User asked for React jobs in Nairobi" }
}

2. Cron Triggers - Scheduled wake-ups

json
{
  "type": "cron",
  "schedule": "0 8 * * *",
  "context": { "task": "morning_briefing" }
}

3. Reminder Triggers - One-time future wake-up

json
{
  "type": "reminder",
  "fire_at": "2026-04-05T10:00:00Z",
  "context": { "task": "follow_up_listing_123" }
}

Event Ingestion

All MCP products emit events to a central endpoint:

typescript
// POST /events/ingest
eventsRouter.post('/ingest', async (req, res) => {
  const { source, event, payload, timestamp } = req.body;
  
  // Find matching triggers (JSON containment query)
  const triggers = await db.query(`
    SELECT * FROM triggers 
    WHERE source = $1 
    AND event = $2 
    AND active = true
    AND conditions <@ $3
  `, [source, event, payload]);
  
  // Queue agent wake-ups
  for (const trigger of triggers) {
    await taskQueue.add('agent-wake', {
      userId: trigger.user_id,
      triggerId: trigger.id,
      source,
      event,
      payload
    });
  }
  
  res.json({ matched: triggers.length });
});

Agent Wake-Up Flow

typescript
async function handleAgentWake(task: AgentTask) {
  const { userId, triggerId, source, event, payload } = task;
  
  // 1. Load agent memory
  const identity = await loadIdentity(userId);  // yeboid-{userId}.md
  const memory = await loadMemory(userId);       // memory.md
  const trigger = await db.triggers.findById(triggerId);
  
  // 2. Build agent context
  const systemPrompt = `
You are this user's personal AI agent on Yebo.

${identity}

Your job is to:
1. Help them with whatever they need
2. Proactively find opportunities for them
3. Learn more about them with every interaction
4. Take actions on their behalf when appropriate

Be personal. Use their name. Remember past conversations.
`;

  const userMessage = `
[TRIGGER FIRED]
Source: ${source}
Event: ${event}
Context: ${JSON.stringify(trigger.context)}

Payload:
${JSON.stringify(payload, null, 2)}

Decide:
1. Is this relevant to them?
2. How should you notify them?
3. Should you take any action via MCP tools?
4. Update your memory if you learn anything new.
`;

  // 3. Run agent turn with MCP tools
  const response = await runAgentTurn(userId, systemPrompt, userMessage);
  
  // 4. Handle outputs
  if (response.notification) {
    await sendInAppNotification(userId, response.notification);
  }
  if (response.memoryUpdate) {
    await saveMemory(userId, response.memoryUpdate);
  }
  if (response.newTriggers) {
    await createTriggers(userId, response.newTriggers);
  }
}

Learning Pipeline

Every interaction updates the user's identity:

typescript
interface LearningEvent {
  userId: string;
  source: string;        // 'yeboshops', 'yebojobs', 'chat', etc.
  event: string;         // 'listing.created', 'message.sent', etc.
  payload: any;
}

async function learn(event: LearningEvent): Promise<void> {
  const identity = await loadIdentity(event.userId);
  
  // Extract insights based on event type
  const insights = await extractInsights(event);
  
  // Update identity file
  const updatedIdentity = mergeInsights(identity, insights);
  
  await saveIdentity(event.userId, updatedIdentity);
}

AI-Powered Learning (for conversations)

typescript
async function learnFromConversation(userId: string, messages: Message[]) {
  const currentIdentity = await loadIdentity(userId);
  
  const prompt = `
You are updating a user's identity file based on a conversation.

CURRENT IDENTITY:
${currentIdentity}

CONVERSATION:
${formatMessages(messages)}

Extract any NEW information about the user:
- Preferences they expressed
- Goals or intentions
- Personal details shared
- Behavior patterns observed

Output as JSON patches:
{
  "updates": [
    { "section": "Preferences", "key": "...", "value": "...", "reason": "..." }
  ]
}
`;

  const response = await gemini.generate(prompt);
  await applyPatches(userId, JSON.parse(response).updates);
}

Agent Tools

The agent has tools to manage itself:

typescript
const agentTools = [
  {
    name: 'create_trigger',
    description: 'Create a trigger to wake me up when something happens',
    parameters: {
      type: { enum: ['event', 'cron', 'reminder'] },
      source: { type: 'string' },
      event: { type: 'string' },
      conditions: { type: 'object' },
      schedule: { type: 'string' },
      fire_at: { type: 'string' },
      context: { type: 'object' }
    }
  },
  {
    name: 'list_triggers',
    description: 'List my active triggers'
  },
  {
    name: 'delete_trigger',
    description: 'Remove a trigger',
    parameters: { triggerId: { type: 'string' } }
  },
  {
    name: 'update_memory',
    description: 'Update my long-term memory about this user',
    parameters: {
      append: { type: 'string' },
      replace: { type: 'string' }
    }
  }
];

Plus all MCP tools from connected products (YeboShops, YeboJobs, etc.)


Example Flow

User: "Let me know if any React jobs in Nairobi come up"

Agent Turn 1:

  1. Loads yeboid-{userId}.md
  2. Understands user wants job alerts
  3. Calls create_trigger:
    json
    {
      "type": "event",
      "source": "yebojobs",
      "event": "job.posted",
      "conditions": { "skills": ["react"], "location": "nairobi" }
    }
  4. Updates identity: "Looking for React developer jobs in Nairobi"
  5. Responds: "I'll watch for React jobs in Nairobi!"

...3 days later...

YeboJobs posts job → webhook → trigger matches → agent wakes

Agent Turn 2:

  1. Loads identity (knows user wants React jobs)
  2. Sees trigger context + job payload
  3. Responds: "🎯 Found a job! React Developer at Safaricom. Want me to apply?"

Framework Package Structure

@yebo/agent-framework/
├── identity/
│   ├── schema.ts          # Identity file structure
│   ├── loader.ts          # Load/save identity
│   └── validator.ts       # Validate identity data
├── learning/
│   ├── pipeline.ts        # Learning event processing
│   ├── extractors/        # Per-product insight extractors
│   ├── ai-learner.ts      # AI-powered extraction
│   └── sync.ts            # Cross-product sync
├── triggers/
│   ├── schema.ts          # Trigger types
│   ├── evaluator.ts       # Match events to triggers
│   └── scheduler.ts       # Cron/reminder processing
├── runtime/
│   ├── context.ts         # Build agent context
│   ├── executor.ts        # Run agent turns
│   └── tools.ts           # Agent tools
├── storage/
│   ├── r2.ts              # R2 operations
│   └── postgres.ts        # DB operations
└── index.ts               # Framework entry

Cost Model

ComponentCost
R2 Storage (100KB × 1M users)~$1.50/mo
Postgres (triggers, metadata)~$20/mo
Event Evaluator (Cloud Run)~$10/mo
Task Queue (Cloud Tasks)~$5/mo
AI Turns (variable)~$0.01/turn

Per-user cost:

  • Average: 5 turns/month = $0.05/user/month
  • Power user: 50 turns/month = $0.50/user/month

Implementation Phases

Phase 1: Storage + Identity

  • [ ] R2 bucket setup
  • [ ] Identity file schema
  • [ ] Load/save functions

Phase 2: Triggers

  • [ ] Postgres triggers table
  • [ ] Event ingestion endpoint
  • [ ] Trigger evaluator

Phase 3: Agent Runtime

  • [ ] Context builder
  • [ ] Agent turn executor
  • [ ] Agent tools (create_trigger, update_memory, etc.)

Phase 4: Learning

  • [ ] Learning pipeline
  • [ ] Per-product extractors
  • [ ] AI-powered conversation learning

Phase 5: MCP Integration

  • [ ] Wire YeboShops events
  • [ ] Wire YeboJobs events
  • [ ] Add more products

The Compound Effect

Day 1:   User signs up → empty identity file
Day 7:   Searches for jobs → learns skills, interests
Day 14:  Lists an iPhone → learns seller behavior
Day 30:  Agent knows preferences, patterns, goals
Day 90:  Agent is their digital economic twin
Day 365: Agent has more context than any human assistant

Why This Wins

  1. Personalization at scale - Every user gets a personal agent
  2. Cross-product intelligence - Learning compounds across all Yebo products
  3. Proactive, not reactive - Agent finds opportunities, not just responds
  4. Cheap to operate - Serverless = pay per use
  5. Moat - User data is the moat. The longer someone uses Yebo, the more their agent knows

"The best founders build systems that compound. This compounds knowledge."


Advanced: Vector Memory + AI Synthesis

The Problem with Basic Design

  • Captures explicit: "User listed an iPhone"
  • Misses implicit: "User always prices 10% below market, responds faster to serious buyers"

Three Memory Layers

User Interaction

┌──────┴──────────────┐
↓                     ↓
Immediate Update      Embed & Store
(structured data)     (semantic vectors)
       ↓                     ↓
   yeboid.md          Vector DB (pgvector)
       ↓                     ↓
       └──────┬──────────────┘

    Periodic AI Synthesis
    (Gemini analyzes everything)

    Updated yeboid.md
    (with discovered insights)

Layer 1: Immediate (Real-time)

Direct updates from events:

  • listing.created → increment seller_count
  • job.applied → add to applications list

Layer 2: Semantic (Vectors)

Embed and store for pattern recognition:

  • Conversation summaries
  • Search queries
  • Browsing behavior
  • Listing descriptions they viewed

What vectors capture that rules can't:

  • User searches "cheap reliable car" + views Toyota listings + skips BMWs → practical, budget-conscious
  • User messages sellers at 10pm, responds within minutes → night owl, decisive
  • User's job searches + items sold correlate → selling to fund career transition

Layer 3: Synthesis (Periodic AI)

Weekly/monthly, Gemini analyzes everything and discovers hidden patterns.

Vector Storage (pgvector)

sql
CREATE EXTENSION vector;

CREATE TABLE user_embeddings (
  id UUID PRIMARY KEY,
  user_id UUID NOT NULL,
  content_type TEXT,  -- 'conversation', 'search', 'behavior'
  content_summary TEXT,
  embedding vector(768),  -- Gemini embedding dimension
  metadata JSONB,
  created_at TIMESTAMPTZ DEFAULT now()
);

CREATE INDEX ON user_embeddings 
USING ivfflat (embedding vector_cosine_ops)
WITH (lists = 100);

Embedding Pipeline

typescript
async function embedAndStore(userId: string, content: string, type: string) {
  const embedding = await gemini.embed(content);
  
  await db.query(`
    INSERT INTO user_embeddings (user_id, content_type, content_summary, embedding)
    VALUES ($1, $2, $3, $4)
  `, [userId, type, summarize(content), embedding]);
}

async function onConversationEnd(userId: string, messages: Message[]) {
  const summary = await summarizeConversation(messages);
  await embedAndStore(userId, summary, 'conversation');
}

Periodic Synthesis

typescript
async function synthesizeUserInsights(userId: string) {
  const identity = await loadIdentity(userId);
  const clusters = await findSemanticClusters(userId);
  const activity = await getRecentActivity(userId, 30);
  
  const prompt = `
You are analyzing a Yebo user to discover hidden patterns.

CURRENT PROFILE:
${identity}

SEMANTIC PATTERNS (from embeddings):
${formatClusters(clusters)}

RECENT ACTIVITY:
${formatActivity(activity)}

Provide:
1. New personality insights
2. Hidden preferences discovered  
3. Behavioral patterns
4. Predictions for next 30 days
5. Specific updates to their profile
`;

  const insights = await gemini.generate(prompt);
  await appendToIdentity(userId, '## AI Insights', insights);
}

Example AI Synthesis Output

markdown
## Agent Notes (AI Synthesized - Apr 2026)
- Likely planning career move to fintech (job search + selling assets pattern)
- Price-sensitive on purchases but values quality (browses premium, waits for deals)
- Decision maker - average 2.3 hours from first view to purchase
- Trust builder - always asks for verification before high-value transactions
- Prediction: Will likely search for laptops soon (new job preparation pattern)

Cost Estimate (1M users)

ComponentCost
pgvector storage~$20/mo (part of existing Postgres)
Embedding API calls~$1,000/mo
Weekly synthesis (active users only)~$4,000/mo
Total~$5,000/mo

Optimization

  • Only synthesize users with >10 new events since last synthesis
  • Use Gemini Flash for routine synthesis
  • Use Gemini Pro only for high-value users
  • Batch embeddings (cheaper than individual calls)

One chat. Everything done.