⏱️ Estimated Reading Time: 12 minutes

Introduction

Pepper is revolutionizing how we interact with AI assistants. Unlike traditional chatbots that passively wait for your commands, Pepper proactively works for you in the background—continuously monitoring your Gmail, summarizing important emails, surfacing critical updates, and autonomously handling complex tasks through a swarm of specialized workers.

Developed by the Agentica team at Berkeley Sky Computing Lab, Pepper represents a fundamental shift from reactive, request-driven systems to real-time, event-driven architectures. This tutorial will guide you through setting up your own Pepper instance and understanding its powerful architecture.

What Makes Pepper Different?

Traditional Chatbots:

  • Wait for user prompts
  • Static and reactive
  • Conversation ends when you stop asking

Pepper (Next-Gen Proactive Assistant):

  • Runs continuously in the background
  • Dynamic and proactive
  • Keeps working even when you’re not actively engaged
  • Handles parallel tasks autonomously
  • Provides context before you even ask

Key Features

Gmail Integration: Automatically monitors and summarizes emails
Proactive Notifications: Surfaces critical updates that need attention
Task Delegation: Assigns complex tasks to specialized worker agents
Event-Driven Architecture: Continuous Sense-Think-Act loop
Real-Time Context: Maintains conversation history and user memory
Non-Blocking Design: Responds immediately while processing in background

Prerequisites

Before starting, ensure you have:

  • Operating System: macOS, Linux, or Windows with WSL
  • Python: Version 3.12 or higher
  • Conda: Anaconda or Miniconda installed
  • Gmail Account: For email integration
  • API Keys:
    • OpenAI API key (required)
    • Composio API key (required for tool integrations)

Installation Guide

Step 1: Clone the Repository

First, clone the Pepper repository with its submodules:

git clone --recurse-submodules https://github.com/agentica-org/pepper
cd pepper

Step 2: Set Up Python Environment

Create and activate a dedicated Conda environment:

# Create new environment with Python 3.12
conda create -n pepper python=3.12 pip -y
conda activate pepper

Step 3: Install Episodic Context Store

Pepper relies on Episodic, a context store that serves as the real-time data backbone:

# Install Episodic SDK with semantic search support
cd episodic-sdk
pip install -e .[semantic]

Step 4: Install Pepper Dependencies

Navigate to the Pepper directory and install requirements:

cd ../pepper
pip install -r requirements.txt

Step 5: Start the Context Store

Launch the Episodic context store server:

episodic serve --port 8000

Important: Keep this process running in the terminal. Open a new terminal window for the next steps.

Configuration

Step 1: Set Up Environment Variables

Copy the example environment file:

cd pepper
cp env_var.example.sh env_var.sh

Step 2: Configure API Keys

Edit env_var.sh with your favorite text editor:

# Required: OpenAI API Key
export OPENAI_API_KEY="sk-your-openai-api-key-here"

# Required: Composio API Key (for Gmail and tool integrations)
export COMPOSIO_API_KEY="your-composio-api-key-here"

# Optional: Additional configurations
export EPISODIC_HOST="http://localhost:8000"

Getting Your API Keys:

  1. OpenAI API Key:
  2. Composio API Key:
    • Sign in at composio.dev
    • Create a new Project
    • Generate an API key

Step 3: Load Environment Variables

source env_var.sh

Step 4: Authorize Gmail Access

Run the email service setup (first-time only):

python -m pepper.services.email_service

What to Expect:

  1. You’ll see: "Please authorize Gmail by visiting: [URL]"
  2. Open the URL in your browser
  3. Grant Gmail access permissions
  4. Wait for: "✅ Trigger subscribed successfully."
  5. Press Ctrl+C to stop the process

Running Pepper

Launch Pepper

Start Pepper with a single command:

python -m pepper.launch_pepper

First-Time Setup Note: The initial launch takes approximately 1 minute to build your user profile.

Access the Web UI

Once Pepper is running, open your browser and navigate to:

http://localhost:5050/pepper/ui.html

Remote Server Users: VS Code should automatically port-forward to your local machine.

Stopping Pepper

To stop all Pepper services:

# Press Ctrl+C in the terminal

Understanding Pepper’s Architecture

Pepper’s power comes from its real-time, event-driven architecture built around a continuous Sense-Think-Act loop.

Core Components

1. Feeds (Sense)

Purpose: System’s sensory input layer

Feeds are intelligent pipelines that:

  • Monitor external sources (emails, messages, calendars)
  • Filter out noise
  • Transform raw data into actionable textual signals
  • Maintain high signal-to-noise ratio

Example: When a new email arrives, the Email Feed:

  1. Checks for keywords and sender importance
  2. Determines urgency level
  3. Distills the email into a concise signal:
{
  "id": "evt_a1b2c3d4-e5f6-a7b8-c9d0-e1f2a3b4c5d6",
  "content": "Action requested by Alice on 'Project Phoenix', due tomorrow.",
  "created_at": "2025-10-04T16:46:15Z"
}

2. Scheduler (Think)

Purpose: Central brain and orchestrator

The Scheduler:

  • Maintains a prioritized FIFO event queue
  • Consumes signals from Feeds
  • Enriches events with context (conversation history, user memory)
  • Decides which actions to take
  • Operates in non-blocking mode for instant responsiveness

Key Innovation: Asynchronous Tool Calls

Traditional LLM APIs enforce synchronous tool calls—the conversation must halt until a tool returns. Pepper solves this by:

  • Appending tool invocations to conversation history
  • Continuing to process new events immediately
  • Handling tool results asynchronously when they arrive

Before (Synchronous - BLOCKED):


{
  "role": "assistant",
  "content": "Starting analysis...",
  "tool_calls": [{"id": "tool_1", "function": "run_analysis"}]
},
// Must wait for tool result before accepting new user input
{
  "role": "user",
  "content": "Can you add a filter?"  // ILLEGAL without tool result
}

After (Asynchronous - CONTINUES):

<tool_call>
  id: tool_call_1
  function: run_analysis
</tool_call>

<user_msg>Can you add a filter for last quarter?</user_msg>

<assistant_msg>Sure. I'll add that filter to the analysis.</assistant_msg>

<tool_result>
  id: tool_call_1
  result: {{ "initial_analysis_complete": true }}
</tool_result>

3. Workers (Act)

Purpose: Specialized execution agents

Workers are equipped with tools via MCP (Model Context Protocol) to:

  • Send emails
  • Manage calendar events
  • Search for information
  • Set reminders
  • Execute complex, long-running tasks

Two Types of Workers:

Stateful Workers: For long-running tasks

  • Maintain memory across interactions
  • Ideal for managing email threads or task lists
  • State persisted in Context Store

Stateless Workers: For one-off jobs

  • Ephemeral and efficient
  • Perfect for quick lookups or single-use tasks
  • Return final answer and terminate

4. Context Store

Purpose: Real-time data serving layer

The Context Store is Pepper’s backbone, similar to feature stores in ML systems but designed for multi-agent orchestration:

  • State Management: Agents persist and share state
  • Real-Time Serving: Immediate access to fresh data
  • Event Orchestration: Updates trigger downstream actions

Core API:

  • store(): Save events to namespaces
  • retrieve(): Query stored contexts
  • subscribe(): Listen for updates

System Flow Example

Let’s walk through how Pepper processes an important email:

Step 1: Email Arrives (Feed - Sense)

# Gmail webhook triggers
@app.on_event("new_email")
async def ingest_raw_event(data: dict):
    await context_store.store(
        context_id=data.get("id", None) or uuid.uuid(),
        data=data,
        namespace="raw.email"
    )

Step 2: Feed Processes Signal

# Email Feed subscribes to raw emails
@subscriber.on_context_update(namespaces=["raw.email"])
async def email_feed(update: ContextUpdate):
    # Find related context using semantic search
    related_docs = await context_store.search(text=update.context.text)
    processed_signal = process_email_signal(update.context, related_docs)
    
    # Emit processed signal
    await context_store.store(
        context_id="processed_" + update.context.context_id,
        data=processed_signal,
        namespace="signals.processed"
    )

Step 3: Scheduler Reacts (Scheduler - Think)

# Scheduler adds signal to priority queue
@subscriber.on_context_update(namespaces=["signals.*"])
async def add_to_queue(self, update: ContextUpdate):
    priority = determine_priority(update.context.data)
    await self.priority_queue.put((priority, update))

# Scheduler continuously processes queue
async def scheduler_step(self):
    events = await self.get_batch_events()
    self.state_tracker.add_events(events)
    
    messages = [
        {"role": "system", "content": SCHEDULER_SYSTEM_PROMPT},
        {"role": "user", "content": self.state_tracker.get_user_prompt()},
    ]
    
    response = await create_completion(messages, self.tools)
    self.state_tracker.add_event(response)
    
    # Schedule tool calls for background execution
    if response.tool_calls:
        for tool_call in response.tool_calls:
            self.tool_call_engine.schedule(tool_call)

Step 4: Worker Executes Action (Worker - Act)

# Worker sends notification to user
await worker.execute_tool("send_notification", {
    "message": "Urgent email from Alice about Project Phoenix - deadline tomorrow",
    "priority": "high"
})

Practical Use Cases

1. Email Management

Scenario: You receive 50+ emails daily, many unimportant.

How Pepper Helps:

  • Continuously monitors Gmail
  • Filters based on sender importance and keywords
  • Summarizes only critical emails
  • Surfaces action items with deadlines
  • Notifies you proactively

2. Meeting Preparation

Scenario: You have a meeting in 2 hours.

How Pepper Helps:

  • Detects meeting from calendar
  • Searches related emails and documents
  • Prepares briefing document
  • Surfaces key discussion points
  • Alerts you 30 minutes before

3. Task Delegation

Scenario: You need to analyze a large dataset.

How Pepper Helps:

  • Accepts task description
  • Delegates to specialized Data Worker
  • Worker runs analysis in background
  • You continue other conversations
  • Receives proactive update when complete

4. Follow-up Management

Scenario: You’re waiting for responses from multiple people.

How Pepper Helps:

  • Tracks pending follow-ups
  • Monitors incoming emails
  • Matches responses to original requests
  • Notifies you when all responses received
  • Summarizes findings

Advanced Configuration

Customizing Feeds

Create custom feeds for different data sources:

# Custom Slack Feed Example
@subscriber.on_context_update(namespaces=["raw.slack"])
async def slack_feed(update: ContextUpdate):
    message = update.context.data
    
    # Filter for urgent mentions
    if "@urgent" in message["text"] or message["user"] in IMPORTANT_USERS:
        processed_signal = {
            "content": f"Urgent Slack message from {message['user']}: {message['text']}",
            "priority": "high",
            "source": "slack"
        }
        
        await context_store.store(
            data=processed_signal,
            namespace="signals.processed"
        )

Adjusting Scheduler Priority

Modify event priorities in the Scheduler:

def determine_priority(event_data):
    """Custom priority logic"""
    if "urgent" in event_data.get("content", "").lower():
        return 1  # Highest priority
    elif event_data.get("source") == "email":
        return 2
    elif event_data.get("source") == "calendar":
        return 3
    else:
        return 4  # Lowest priority

Creating Custom Workers

Build specialized workers for specific tasks:

class DataAnalysisWorker(StatefulWorker):
    """Worker specialized in data analysis tasks"""
    
    def __init__(self):
        super().__init__()
        self.tools = [
            "run_sql_query",
            "generate_visualization",
            "calculate_statistics"
        ]
    
    async def execute_task(self, task_description):
        # Load previous state if exists
        state = await self.load_state()
        
        # Perform analysis
        result = await self.run_analysis(task_description)
        
        # Save state
        await self.save_state(result)
        
        # Return to scheduler
        return result

Troubleshooting

Common Issues

1. Gmail Authorization Fails

Problem: Cannot authorize Gmail access

Solution:

# Clear existing credentials
rm -rf ~/.credentials/pepper/

# Re-run authorization
python -m pepper.services.email_service

2. Episodic Server Not Running

Problem: Connection refused to localhost:8000

Solution:

# Check if episodic is running
ps aux | grep episodic

# If not running, start it
conda activate pepper
episodic serve --port 8000

3. Missing API Keys

Problem: API key not found errors

Solution:

# Verify environment variables are loaded
echo $OPENAI_API_KEY
echo $COMPOSIO_API_KEY

# If empty, reload environment
source env_var.sh

4. First Launch Takes Too Long

Problem: Initial profile building exceeds 2 minutes

Solution:

  • This is normal for first-time setup
  • Check system resources (CPU, memory)
  • Ensure stable internet connection
  • Wait patiently—subsequent launches are faster

Best Practices

1. Email Management

  • Configure Filters: Set up email filters in Gmail to reduce noise
  • Important Contacts: Mark critical senders as VIP
  • Review Summaries: Check Pepper’s summaries daily
  • Adjust Priorities: Fine-tune what counts as “important”

2. Context Management

  • Regular Cleanup: Periodically clear old context data
  • Namespace Organization: Use clear namespace conventions
  • State Versioning: Version agent states for rollback capability

3. Worker Optimization

  • Task Granularity: Break large tasks into smaller chunks
  • Timeout Settings: Set reasonable timeouts for long-running tasks
  • Error Handling: Implement robust error recovery in workers

4. Security

  • API Key Rotation: Regularly rotate API keys
  • Access Control: Limit Gmail scope to read-only when possible
  • Audit Logs: Monitor Pepper’s actions regularly
  • Data Privacy: Be mindful of sensitive information in emails

Performance Optimization

1. Scheduler Tuning

# Adjust batch size for event processing
SCHEDULER_BATCH_SIZE = 5  # Process 5 events per cycle

# Set max queue size
MAX_QUEUE_SIZE = 100  # Prevent memory issues

2. Context Store Optimization

# Enable semantic search caching
export EPISODIC_CACHE_ENABLED=true
export EPISODIC_CACHE_TTL=3600  # 1 hour

3. Worker Pool Management

# Configure worker pool size
MAX_CONCURRENT_WORKERS = 10
WORKER_TIMEOUT = 300  # 5 minutes

Comparison with Traditional Assistants

Feature Traditional Chatbot Pepper
Interaction Model Reactive (wait for prompt) Proactive (continuous monitoring)
Task Handling Sequential Parallel
Context Awareness Limited to conversation Full context from multiple sources
Response Time Immediate but limited Background processing + instant notifications
Tool Execution Synchronous (blocking) Asynchronous (non-blocking)
Memory Session-based Persistent across sessions
Scalability Single conversation Multiple parallel tasks

Future Enhancements

Pepper’s architecture enables exciting future capabilities:

1. Predictive Feeds

Feeds that anticipate needs before events occur:

  • Schedule meeting prep before calendar invite accepted
  • Prepare travel documents as soon as flight booked
  • Draft responses to common email patterns

2. Multi-User Coordination

Teams working with shared Pepper instances:

  • Collaborative task management
  • Shared context and knowledge base
  • Team-wide notifications and summaries

3. Advanced Learning

Pepper learning from your behavior:

  • Personalized priority settings
  • Custom automation rules
  • Behavioral pattern recognition

Resources

Further Reading

  1. Event-Driven Architecture in Distributed Systems
  2. ChatGPT Pulse: Next-Gen Proactive AI
  3. Uber’s Michelangelo ML Platform

Conclusion

Pepper represents a paradigm shift in AI assistant technology—from passive responders to proactive partners. By leveraging event-driven architecture with its Sense-Think-Act loop, Pepper continuously works for you, handling complex tasks in the background while keeping you informed of what truly matters.

The open-source nature of Pepper, combined with its modular architecture, makes it an ideal platform for experimenting with next-generation agentic systems. Whether you’re looking to automate email management, build custom workflows, or explore real-time AI orchestration, Pepper provides a solid foundation.

Key Takeaways:

✅ Pepper transforms AI assistance from reactive to proactive
✅ Event-driven architecture enables parallel, non-blocking task execution
✅ Modular design (Feeds, Scheduler, Workers, Context Store) allows easy customization
✅ Gmail integration provides immediate practical value
✅ Open-source and actively developed by Berkeley’s Agentica team

Acknowledgments

Pepper is developed by the Agentica Team as part of Berkeley Sky Computing Lab, supported by Laude Institute with compute grants from AWS and Hyperbolic.


Have questions or feedback? Feel free to contribute to the project on GitHub or join the community discussions!

🔗 GitHub: github.com/agentica-org/pepper
📧 Contact: Check the repository for community channels

Next Steps:

  • Set up your Pepper instance following this guide
  • Explore the codebase and customize feeds
  • Build your own workers for specific tasks
  • Share your experience with the community!

Happy building! 🚀