# Project export: Travis.ai

This document was generated by HackStack to give an AI agent context about a hackathon project. Sections are labeled with their provenance; content marked as truncated was cut to keep this document small.

## Project metadata

- Hackathon: Cal Hacks 12.0
- Tagline: Trips that never left the group chat? Not anymore. We make planning easy, fair, and fun — so your crew can finally make memories, not excuses.
- Devpost: https://devpost.com/software/travis-ai
- GitHub: https://github.com/jathinsn27/travis_ai
- Video: https://www.youtube.com/embed/l8kAXLAhT38?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 3 GitHub contributor(s) — Omkar399 (4 commits), jathin sn (3 commits), sivagirish81 (1 commits)

## Devpost submission (written by the team)

### Inspiration

We all have those trips that never made it out of the group chat. Someone’s idea gets ignored, another person’s budget doesn’t match, and before you know it — the whole plan falls apart. Planning a trip that fits everyone’s interests can feel impossible. We wanted to change that. Our goal? To retire those endless, chaotic group chats and bring the fun back to planning. Let’s leave behind the stress, the confusion, and the last-minute cancellations — so you and your friends can focus on what really matters: having an amazing time together. 🌍🎉

### What it does

Ever been chilling on Slack, just vibing with your friends 😎, when suddenly someone goes — “Hey, let’s do a trip!” 🏔️🌊🍹 Boom 💥 — energy’s off the charts, everyone’s throwing ideas: “We should go hiking!” ⛰️ “No, beach day!!” 🏖️ “Let’s find the best food spots!” 🍜 “Don’t forget the night markets!” 🌃 The Slack chat’s a chaotic masterpiece of excitement 🎉💬 … and that’s when you call in Travis. 🤖 Meet Travis — Your AI Travel Buddy Travis jumps into your Slack chat 🕵️‍♂️, reads the whole conversation, and magically figures out: Who’s in the trip gang 🧑‍🤝‍🧑 What each person’s vibe is 🧠 What everyone really wants to do 😏 Then — POOF! 💫 — Travis builds a persona and an AI agent for each friend. Each agent negotiates and collaborates (without the group chat chaos 🌀), ensuring everyone’s interests are included. 📝 Shared in Notion — Because Planning Should Be Pretty When Travis is done planning, your itinerary appears in Notion 📒💎 Complete with: ✅ Checkboxes for every activity 🕒 Timelines and travel slots 📍 Location links and notes 💬 Real-time collaboration with your crew You can tick off adventures as you go! Each check = a memory unlocked 🎯✨ 🧩 How We Built It — The Magic Behind Travis.ai ⚙️💡 Travis.ai isn’t just another travel bot — it’s a whole AI ecosystem built to understand your group’s chaos and turn it into harmony. 🎶✨ Here’s how the magic happens 👇 🧠 1. Persona Agents Powered by Letta We used Letta to create persona agents — one for each user in your Slack group chat. Each persona agent learns from your chat history 🗨️: How you text 😄 What you like 🏝️ What you really mean when you say “I’m fine with anything” (spoiler: you’re not 😉) These agents capture everyone’s unique travel style — the foodie 🍜, the explorer 🏕️, the chill one 🧘, and the “let’s make a spreadsheet” person 📊. Once they’ve learned your personalities, they start talking to each other — yep, the agents negotiate like a mini travel council. 🧑‍⚖️🤝 🗺️ 2. Conflict Mapping & Negotiation Each agent builds an internal conflict map 🕸️ — identifying who wants what, where clashes might happen, and how to find the sweet spot. Travis then acts as a Planner Agent, guiding the group of agents to co-create a travel plan that: Gives everyone at least one activity they’ll love 💖 Minimizes schedule clashes ⚖️ Maximizes group fun 🎢 In short: it’s diplomacy meets vacation planning! 🌍🤝 🔌 3. The MCP Server & Tool Router Magic Once everyone’s done brainstorming their dream trip, Travis switches gears and activates the Planner Agent. Behind the scenes, we’ve got an MCP (Model Context Protocol) server running over Composia’s Tool Router 🧩. This setup connects Slack, Notion, and Bright Data into one smooth system: 💬 Slack → where all the fun starts (the group chat chaos). 🗒️ Notion → where the final itinerary lives (beautiful, editable, and sharable). 🌐 Bright Data → our web-scraping engine that fetches the latest trends, flights, and hotel deals in real-time. ✈️🏨💰 With all that data, Travis doesn’t just plan what to do — it finds the best way to do it.

### Challenges we ran into

Integrating brightdata via composio was particularly difficult. Letta

### Accomplishments we're proud of

🤝 We successfully got individual persona agents to negotiate and collaborate autonomously — each representing a unique user from the group chat. 🧠 Built a conflict-resolution system that balances everyone’s interests, creating trip plans that make everyone happy (yes, even the indecisive one 😅). 🔗 Integrated Slack, Notion, and Bright Data through our MCP + Tool Router setup — turning scattered ideas into a seamless, data-driven travel plan.

### What we learned

🤖 We learned how to design and orchestrate a multi-agent architecture, where each agent has its own goals, personality, and reasoning capabilities. 💬 We explored how to make agents communicate, negotiate, and collaborate effectively — simulating real human dynamics within a digital group. 🧩 We developed a Model Context Protocol (MCP) to seamlessly connect tools and services, enabling agents to pull live data and coordinate across platforms like Slack, Notion, and Bright Data.

### What's next

Enhance the agent to pick up Persona more accurately to negotiate a trip.

## README (from the GitHub repository)

# Travis AI - Letta Agent with Slack Integration

A FastAPI application that integrates Letta AI agents with Slack, providing intelligent conversation capabilities through a single endpoint.

## Features

- 🤖 **Letta AI Integration**: Uses a persistent Letta agent for intelligent conversations
- 💬 **Slack Integration**: Handles Slack URL verification, message events, and app mentions
- 🔄 **Automatic Responses**: Posts Letta agent responses back to Slack channels
- 🎯 **Smart Filtering**: Filters out simple greetings to reduce noise
- 🌐 **ngrok Support**: Easy deployment with static domain support
- 📝 **Comprehensive Logging**: Detailed debug information for troubleshooting

## Project Structure

```
travis_ai/
├── letta/
│   ├── create_agent.py      # Letta agent manager
│   └── api.py               # Legacy API (moved to simple_api.py)
├── slack/
│   └── auth_middleware.py   # Slack authentication middleware
├── simple_api.py            # Main FastAPI application
├── main.py                  # Entry point
├── run.py                   # Simplified startup script
├── test.py                  # Basic API tests
├── requirements.txt         # Python dependencies
├── .env                     # Environment variables (create this)
└── README.md               # This file
```

## Prerequisites

- Python 3.8+
- [uv](https://docs.astral.sh/uv/) package manager
- [ngrok](https://ngrok.com/) account (for Slack integration)
- [Letta AI](https://letta.com/) account
- [Slack App](https://api.slack.com/apps) with bot token

## Setup Instructions

### 1. Clone and Install Dependencies

```bash
# Clone the repository
git clone <your-repo-url>
cd travis_ai

# Install dependencies using uv
uv pip install -r requirements.txt
```

### 2. Environment Configuration

Create a `.env` file in the project root:

```bash
# Letta AI Configuration
LETTA_API_KEY=your_letta_api_key_here

# Slack Configuration  
SLACK_BOT_TOKEN=xoxb-your-slack-bot-token-here

# Optional: ngrok static domain
NGROK_DOMAIN=your-static-domain.ngrok-free.dev
```

### 3. Letta Agent Setup

The application uses a pre-configured Letta agent ID: `agent-2ed1f26b-6de6-4d1a-8466-b89e12482f71`

If you need to create a new agent, you can use the `letta/create_agent.py` script:

```bash
uv run python letta/create_agent.py
```

### 4. Slack App Configuration

1. Create a Slack app at [api.slack.com/apps](https://api.slack.com/apps)
2. Add the following OAuth scopes:
   - `app_mentions:read`
   - `channels:history`
   - `chat:write`
   - `im:history`
   - `im:read`
   - `im:write`
3. Subscribe to bot events:
   - `message.channels`
   - `app_mention`
4. Set the Request URL to your ngrok URL + `/conversation`

## Running the Application

### Option 1: Local Development

```bash
# Run locally without ngrok
uv run python simple_api.py

# Or use the main entry point
uv run python main.py
```

### Option 2: With ngrok (for Slack integration)

```bash
# Run with ngrok tunneling
uv run python simple_api.py --ngrok

# Or use the main entry point with ngrok
uv run python main.py --ngrok
```

### Option 3: Using the simplified runner

```bash
# Local
uv run python run.py

# With ngrok
uv run python run.py --ngrok
```

## API Endpoints

### POST `/conversation`

The main endpoint that handles all interactions:

**Slack URL Verification:**
```json
{
  "type": "url_verification",
  "challenge": "challenge_string"
}
```

**Slack Message Events:**
```json
{
  "type": "event_callback",
  "event": {
    "type": "message",
    "user": "U1234567890",
    "text": "Hello, how are you?",
    "channel": "C1234567890"
  }
}
```

**Direct Conversation:**
```json
{
  "message": "What is Python programming?"
}
```

### GET `/health`

Health check endpoint that returns:
```json
{
  "status": "healthy",
  "message": "Simple Letta Agent API is running",
  "agent_id": "agent-2ed1f26b-6de6-4d1a-8466-b89e12482f71"
}
```

## Testing

Run the basic test suite:

```bash
uv run python test.py
```

This will test:
- Health check endpoint
- URL verification
- Simple greeting filtering
- Direct conversation with Letta agent

## Slack Integration

### Event Types Handled

1. **URL Verification**: Responds to Slack's challenge requests
2. **Message Events**: Processes regular channel messages
3. **App Mentions**: Handles `@bot` mentions specifically

### Message Filtering

The application filters out simple greetings to reduce noise:
- `hi`, `hey`, `hiya`
- `good morning/afternoon/evening`
- `what's up`, `how are you`
- `thanks`, `ok`, `yes`, `no`

### Response Posting

- Responses are automatically posted back to Slack
- Uses threading to keep conversations organized
- Includes comprehensive logging for debugging

## Troubleshooting

### Common Issues

1. **ModuleNotFoundError**: Ensure all dependencies are installed with `uv pip install -r requirements.txt`

2. **LETTA_API_KEY not set**: Check your `.env` file and ensure the API key is correct

3. **Slack events not received**: 
   - Verify ngrok is running and accessible
   - Check Slack app configuration
   - Ensure Request URL points to `/conversation`

4. **Agent not responding**: 
   - Verify the agent ID is correct
   - Check Letta API key permissions
   - Review logs for error messages

### Debug Logging

The application provides extensive debug logging:
- `🔍 DEBUG:` - Detailed request/response information
- `✅` - Successful operations
- `❌` - Errors and failures
- `📨` - Slack message processing
- `🤖` - Letta agent responses

## Development

### Code Structure

- `simple_api.py`: Main FastAPI application with single endpoint
- `letta/create_agent.py`: Letta agent management and API interactions
- `slack/auth_middleware.py`: Slack authentication and middleware
- `main.py`: Application entry point with argument parsing
- `run.py`: Simplified startup script

### Key Components

1. **LettaAgentManager**: Handles all Letta API interactions
2. **Message Filtering**: Smart filtering to reduce noise
3. **Slack Integration**: URL verification and event handling
4. **Response Posting**: Automatic Slack message posting
5. **Error Handling**: Comprehensive error handling and logging

## Deployment

### Local Development
```bash
uv run python simple_api.py --ngrok
```

### Production Considerations
- Use a proper WSGI server like Gunicorn
- Set up proper environment variable management
- Configure SSL/TLS for HTTPS
- Use a proper domain instead of ngrok for production

## Contributing

1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Test thoroughly
5. Submit a pull request

## License

This project is licensed under the MIT License.

## Support

For issues and questions:
1. Check the troubleshooting section
2. Review the debug logs
3. Verify environment configuration
4. Test with the provided test suite

## Detected evidence (automated analysis)

Indexed codebase: 23 recognized source files, 203 KB.
- FastAPI (technology) — detected in the code
- OpenAI (technology) — detected in the code
- Python (language) — detected in the code

## Codebase structure (from repository index)

### Files (39 of 39)

```
.env.example
.gitignore
letta/agent_ids.json
letta/api.py
letta/bot_trigger_handler.py
letta/create_agent.py
letta/create_personas_with_shared_memory.py
letta/dedicated_planner_agent.py
letta/letta_ai.py
letta/persona_mapper.py
letta/rename_chats_to_summary.py
letta/slack_dynamic_persona_integration.py
letta/start_api.py
letta/test_pipeline.py
letta/update_shared_memory.py
letta/user_message_handler.py
main.py
NGROK_SETUP.md
pyproject.toml
README.md
requirements.txt
run.py
simple_api.py
slack/auth_middleware.py
test.py
tool-router/AUTHENTICATION.txt
tool-router/BRIGHTDATA_BROWSER_GUIDE.txt
tool-router/BRIGHTDATA_GUIDE.txt
tool-router/COMPLETE_GUIDE.txt
tool-router/main.py
tool-router/NOTION_GUIDE.txt
tool-router/README.txt
tool-router/requirements.txt
tool-router/SETUP.txt
tool-router/test_brightdata_browser.py
tool-router/test_connection.py
tool-router/USAGE_GUIDE.txt
tool-router/YOUR_NOTION_PAGE.txt
uv.lock
```

### Dependencies

- pyproject.toml: fastapi@>=0.120.0, httpx@>=0.28.1, letta-client@>=0.1.324, pyngrok@>=7.4.1, python-dotenv@>=1.1.1, requests@>=2.32.5, slack-sdk@>=3.37.0, uvicorn@>=0.38.0
- requirements.txt: fastapi, letta-client@>=0.1.324, pyngrok, python-dotenv, requests, slack-sdk, uvicorn
- tool-router/requirements.txt: aiohappyeyeballs@==2.6.1, aiohttp@==3.13.1, aiosignal@==1.4.0, annotated-doc@==0.0.3, annotated-types@==0.7.0, anyio@==4.11.0, attrs@==25.4.0, bcrypt@==5.0.0, certifi@==2025.10.5, cffi@==2.0.0, charset-normalizer@==3.4.4, click@==8.3.0, composio@==0.9.0, composio_core@==0.7.21, composio-client@==1.10.0, composio-openai@==0.9.0, cryptography@==46.0.3, distro@==1.9.0, fastapi@==0.120.0, frozenlist@==1.8.0, h11@==0.16.0, httpcore@==1.0.9, httpx@==0.28.1, httpx-sse@==0.4.3, idna@==3.11, importlib_metadata@==8.7.0, inflection@==0.5.1, invoke@==2.2.1, jiter@==0.11.1, jsonref@==1.1.0, jsonschema@==4.25.1, jsonschema-specifications@==2025.9.1, markdown-it-py@==4.0.0, mcp@==1.19.0, mdurl@==0.1.2, multidict@==6.7.0, openai@==2.6.1, paramiko@==4.0.0, pillow@==10.4.0, propcache@==0.4.1, pycparser@==2.23, pydantic@==2.12.3, pydantic_core@==2.41.4, pydantic-settings@==2.11.0, Pygments@==2.19.2, PyNaCl@==1.6.0, pyperclip@==1.11.0, Pysher@==1.0.8, python-dotenv@==1.1.1, python-multipart@==0.0.20, PyYAML@==6.0.3, referencing@==0.37.0, requests@==2.32.5, rich@==13.9.4, rpds-py@==0.28.0, semver@==3.0.4, sentry-sdk@==2.42.1, sniffio@==1.3.1, sse-starlette@==3.0.2, starlette@==0.48.0, tqdm@==4.67.1, typing_extensions@==4.15.0, typing-inspection@==0.4.2, urllib3@==2.5.0, uvicorn@==0.38.0, websocket-client@==1.9.0, yarl@==1.22.0, zipp@==3.23.0

### Recent commits (newest first)

- Merge branch 'main' of github.com:jathinsn27/travis_ai
- Complete Letta Architecuture
- Added tool-router changes
- fix test
- fix dupli
- multiple agents
- make ngrok configurable
- initial commit

## Key source files (fetched from GitHub, selected and truncated for size)

### NGROK_SETUP.md

```markdown
# Ngrok Domain Configuration Guide

## Overview

All hardcoded ngrok domains have been replaced with the `NGROK_DOMAIN` environment variable. This makes it easy to switch between different ngrok domains without modifying code.

## Files Updated

### 1. **letta/api.py**
- Line 375: `slack_install()` endpoint now reads `NGROK_DOMAIN` from environment
- Lines 390-395: `start_server()` function uses environment variable for ngrok connection

### 2. **simple_api.py**
- Lines 406-411: `start_server()` function uses environment variable for ngrok connection

### 3. **slack/auth_middleware.py**
- Line 131: `auth_slack_install()` endpoint reads `NGROK_DOMAIN` from environment
- Lines 147-152: `start_auth_server()` function uses environment variable for ngrok connection

## Environment Variables

### .env File

Create a `.env` file in the project root with:

```bash
# Ngrok Configuration
NGROK_DOMAIN=your-static-domain.ngrok-free.dev

# Slack Configuration
SLACK_BOT_TOKEN=xoxb-your-slack-bot-token-here
SLACK_SIGNING_SECRET=your-slack-signing-secret-here

# Letta Configuration
LETTA_API_KEY=your-letta-api-key-here
LETTA_API_URL=http://localhost:8090

# Server Configuration (Optional)
API_HOST=0.0.0.0
API_PORT=8000
AUTH_MIDDLEWARE_PORT=8001
```

### Getting a Ngrok Static Domain

1. Go to https://dashboard.ngrok.com
2. Sign up or log in to your ngrok account
3. Navigate to "Domains" section
4. Create a new domain (e.g., `your-static-domain.ngrok-free.dev`)
5. Copy the domain name and add it to your `.env` file as `NGROK_DOMAIN`

## Usage

### With Environment Variable Set

When `NGROK_DOMAIN` is set in your `.env` file:

```bash
uv run python simple_api.py --ngrok
# Output: 🌐 Ngrok tunnel created with static domain: https://your-static-domain.ngrok-free.dev
```

### Without Environment Variable

If `NGROK_DOMAIN` is not set:

```bash
uv run python simple_api.py --ngrok
# Output: ⚠️  NGROK_DOMAIN not set, using random domain
# Output: 🌐 Ngrok tunnel created: https://random-domain.ngrok-free.dev
```

The application automatically falls back to a random ngrok domain if the static domain is not available.

## Slack Configuration

When setting up your Slack app, use the URLs from the following endpoints:

- **Events URL**: `https://{NGROK_DOMAIN}/conversation`
- **Install URL**: `https://{NGROK_DOMAIN}/slack/install`

These URLs will automatically use the `NGROK_DOMAIN` value from your `.env` file.

## Benefits

✅ **Easy Domain Switching**: Change your ngrok domain without code changes  
✅ **Environment-Specific Config**: Different domains for dev/staging/prod  
✅ **Secure**: No hardcoded domains in version control  
✅ **Flexible**: Works with or without static domains  

## Troubleshooting

### "NGROK_DOMAIN not set" Warning

- Check that `.env` file exists in the project root
- Verify `NGROK_DOMAIN` is properly set in `.env`
- Check that the variable is not commented out with `#`

### Ngrok Connection Error

If you get an error like "Invalid domain":
1. Verif
[truncated — 481 more characters]
```

### requirements.txt

```
# Dependencies managed by uv
# Run: uv sync to install dependencies
# Run: uv run python script.py to run scripts

letta-client>=0.1.324
fastapi
uvicorn
pyngrok
python-dotenv
requests
slack-sdk

```

### pyproject.toml

```
[project]
name = "travis-ai"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
    "fastapi>=0.120.0",
    "httpx>=0.28.1",
    "letta-client>=0.1.324",
    "pyngrok>=7.4.1",
    "python-dotenv>=1.1.1",
    "requests>=2.32.5",
    "slack-sdk>=3.37.0",
    "uvicorn>=0.38.0",
]

```

### tool-router/requirements.txt

```
aiohappyeyeballs==2.6.1
aiohttp==3.13.1
aiosignal==1.4.0
annotated-doc==0.0.3
annotated-types==0.7.0
anyio==4.11.0
attrs==25.4.0
bcrypt==5.0.0
certifi==2025.10.5
cffi==2.0.0
charset-normalizer==3.4.4
click==8.3.0
composio==0.9.0
composio-client==1.10.0
composio-openai==0.9.0
composio_core==0.7.21
cryptography==46.0.3
distro==1.9.0
fastapi==0.120.0
frozenlist==1.8.0
h11==0.16.0
httpcore==1.0.9
httpx==0.28.1
httpx-sse==0.4.3
idna==3.11
importlib_metadata==8.7.0
inflection==0.5.1
invoke==2.2.1
jiter==0.11.1
jsonref==1.1.0
jsonschema==4.25.1
jsonschema-specifications==2025.9.1
markdown-it-py==4.0.0
mcp==1.19.0
mdurl==0.1.2
multidict==6.7.0
openai==2.6.1
paramiko==4.0.0
pillow==10.4.0
propcache==0.4.1
pycparser==2.23
pydantic==2.12.3
pydantic-settings==2.11.0
pydantic_core==2.41.4
Pygments==2.19.2
PyNaCl==1.6.0
pyperclip==1.11.0
Pysher==1.0.8
python-dotenv==1.1.1
python-multipart==0.0.20
PyYAML==6.0.3
referencing==0.37.0
requests==2.32.5
rich==13.9.4
rpds-py==0.28.0
semver==3.0.4
sentry-sdk==2.42.1
sniffio==1.3.1
sse-starlette==3.0.2
starlette==0.48.0
tqdm==4.67.1
typing-inspection==0.4.2
typing_extensions==4.15.0
urllib3==2.5.0
uvicorn==0.38.0
websocket-client==1.9.0
yarl==1.22.0
zipp==3.23.0

```

### main.py

```python
#!/usr/bin/env python3
"""
Main entry point for Travis AI - Letta Agent with Slack Integration
"""

import sys
import os
from dotenv import load_dotenv

# Load environment variables
load_dotenv()

def main():
    """Main function to start the API"""
    print("🚀 Starting Travis AI - Letta Agent with Slack Integration")
    print("=" * 60)
    
    # Check for required environment variables
    required_vars = ["LETTA_API_KEY"]
    missing_vars = []
    
    for var in required_vars:
        if not os.getenv(var):
            missing_vars.append(var)
    
    if missing_vars:
        print(f"❌ Missing required environment variables: {', '.join(missing_vars)}")
        print("   Please add them to your .env file")
        return 1
    
    # Check for optional Slack token
    if not os.getenv("SLACK_BOT_TOKEN"):
        print("⚠️  SLACK_BOT_TOKEN not found - Slack posting will be disabled")
        print("   Add SLACK_BOT_TOKEN to .env file to enable Slack responses")
    else:
        print("✅ SLACK_BOT_TOKEN found - Slack posting enabled")
    
    # Start the API
    try:
        from simple_api import start_server
        
        use_ngrok = "--ngrok" in sys.argv
        start_server(use_ngrok=use_ngrok)
        
    except KeyboardInterrupt:
        print("\n👋 Shutting down...")
        return 0
    except Exception as e:
        print(f"❌ Error starting server: {e}")
        return 1

if __name__ == "__main__":
    sys.exit(main())

```

### tool-router/main.py

```python
#!/usr/bin/env python3
"""
MCP Server for Composio Tool Router
Exposes Notion and Slack tools via Model Context Protocol
"""
import asyncio
import os
import sys
import json
from pathlib import Path
from composio import Composio
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent

def load_env():
    """Load environment variables from .env file"""
    env_file = Path(__file__).parent / '.env'
    if env_file.exists():
        with open(env_file) as f:
            for line in f:
                line = line.strip()
                if line and not line.startswith('#') and '=' in line:
                    key, value = line.split('=', 1)
                    os.environ[key.strip()] = value.strip()

# Load environment variables
load_env()

# Initialize Composio
api_key = os.getenv("COMPOSIO_API_KEY")
if not api_key:
    sys.stderr.write("ERROR: COMPOSIO_API_KEY not set\n")
    sys.exit(1)

# Create MCP server
app = Server("composio-tool-router")
composio_client = Composio(api_key=api_key)

# Create Tool Router session
try:
    session = composio_client.experimental.tool_router.create_session(
        user_id="default_user",
        toolkits=["notion", "slack", "brightdata"]
    )
    TOOL_ROUTER_URL = session.get('url', '')
except Exception as e:
    sys.stderr.write(f"Error creating Tool Router session: {e}\n")
    TOOL_ROUTER_URL = None

@app.list_tools()
async def list_tools() -> list[Tool]:
    """List available Composio Tool Router meta tools"""
    return [
        Tool(
            name="COMPOSIO_SEARCH_TOOLS",
            description="Discovers tools across Notion, Slack, and BrightData based on task description. Returns relevant tools with their schemas, connection status, and descriptions. All three apps are connected for entity 'default_user'.",
            inputSchema={
                "type": "object",
                "properties": {
                    "query": {
                        "type": "string",
                        "description": "Description of what you want to do (e.g., 'create notion page', 'send slack message', 'scrape website', 'serp search')"
                    }
                },
                "required": ["query"]
            }
        ),
        Tool(
            name="COMPOSIO_MANAGE_CONNECTIONS",
            description="Manages authentication connections to Notion, Slack, and BrightData. Returns OAuth URLs when authentication is needed.",
            inputSchema={
                "type": "object",
                "properties": {
                    "toolkits": {
                        "type": "array",
                        "items": {"type": "string"},
                        "description": "List of toolkits to connect (e.g., ['notion', 'slack', 'brightdata'])"
                    },
                    "action": {
                        "type": "string",
                        "enum": ["create", "check", "refresh"],
                        "description": "Action to perform: create new connection, check status, or refresh token"
                    }
                },
                "required": ["toolkits", "action"]
            }
        ),
        Tool(
            name="COMPOSIO_MULTI_EXECUTE_TOOL",
            description="Executes up to 20 tools in parallel across Notion, Slack, and BrightData. Use this after discovering tools with COMPOSIO_SEARCH_TOOLS. Can create Notion pages, send Slack messages, scrape websites, perform SERP searches, and more.",
            inputSchema={
                "type": "object",
                "properties": {
                    "tools": {
                        "type": "array",
                        "items": {
                            "type": "object",
                            "properties": {
                                "name": {"type": "string", "description": "Tool name from COMPOSIO_SEARCH_TOOLS"},
                                "input": {"type": "object", "description": "Tool parameters"}
                            }
                        },
                        "description": "List of tools to execute"
                    },
                    "execute_parallel": {
                        "type": "boolean",
                        "description": "Whether to execute tools in parallel (default: true)"
                    }
                },
                "required": ["tools"]
            }
        ),
        Tool(
            name="BRIGHTDATA_BROWSER_WORKFLOW",
            description="Advanced browser automation workflows using BrightData. Combines multiple browser tools for complex scraping tasks, data extraction, and analysis.",
            inputSchema={
                "type": "object",
                "properties": {
                    "workflow_type": {
                        "type": "string",
                        "enum": ["scrape_and_analyze", "serp_monitoring", "ecommerce_tracking", "social_media_analysis", "competitor_research"],
                        "description": "Type of browser workflow to execute"
                    },
                    "targets": {
                        "type": "array",
                        "items": {"type": "string"},
                        "description": "URLs or search queries to process"
                    },
                    "options": {
                        "type": "object",
                        "description": "Workflow-specific options (screenshot, extract_links, follow_redirects, etc.)"
                    },
                    "output_format": {
                        "type": "string",
                        "enum": ["json", "html", "text", "notion_page", "slack_message"],
                        "description": "How to format and deliver results"
                    }
                },
                "required": ["workflow_type", "targets"]
            }
        )
    ]

@app.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextConten
[truncated — 25437 more characters]
```

### run.py

```python
#!/usr/bin/env python3
"""
Simplified startup script for the Letta Agent API
"""

import sys
import os
from dotenv import load_dotenv

# Load environment variables
load_dotenv()

def main():
    """Main function to start the API"""
    print("🚀 Starting Travis AI - Letta Agent with Slack Integration")
    print("=" * 60)
    
    # Check for required environment variables
    required_vars = ["LETTA_API_KEY"]
    missing_vars = []
    
    for var in required_vars:
        if not os.getenv(var):
            missing_vars.append(var)
    
    if missing_vars:
        print(f"❌ Missing required environment variables: {', '.join(missing_vars)}")
        print("   Please add them to your .env file")
        return 1
    
    # Check for optional Slack token
    if not os.getenv("SLACK_BOT_TOKEN"):
        print("⚠️  SLACK_BOT_TOKEN not found - Slack posting will be disabled")
        print("   Add SLACK_BOT_TOKEN to .env file to enable Slack responses")
    else:
        print("✅ SLACK_BOT_TOKEN found - Slack posting enabled")
    
    # Start the API
    try:
        from simple_api import start_server
        
        use_ngrok = "--ngrok" in sys.argv
        start_server(use_ngrok=use_ngrok)
        
    except KeyboardInterrupt:
        print("\n👋 Shutting down...")
        return 0
    except Exception as e:
        print(f"❌ Error starting server: {e}")
        return 1

if __name__ == "__main__":
    sys.exit(main())

```

### test.py

```python
#!/usr/bin/env python3
"""
Simple test script for the Letta Agent API
"""

import requests
import json

def test_api(base_url="http://localhost:8000"):
    """Test the API endpoints"""
    print("🧪 Testing Letta Agent API")
    print("=" * 40)
    
    # Test 1: Health check
    print("\n1️⃣ Testing health check...")
    try:
        response = requests.get(f"{base_url}/health")
        if response.status_code == 200:
            print("✅ Health check passed")
            print(f"   Response: {response.json()}")
        else:
            print(f"❌ Health check failed: {response.status_code}")
    except Exception as e:
        print(f"❌ Health check error: {e}")
    
    # Test 2: Slack URL verification
    print("\n2️⃣ Testing Slack URL verification...")
    try:
        challenge_data = {
            "type": "url_verification",
            "challenge": "test_challenge_12345"
        }
        response = requests.post(
            f"{base_url}/conversation",
            json=challenge_data,
            headers={"Content-Type": "application/json"}
        )
        
        if response.status_code == 200:
            data = response.json()
            if data.get("challenge") == "test_challenge_12345":
                print("✅ Slack URL verification working")
            else:
                print(f"❌ URL verification failed: {data}")
        else:
            print(f"❌ URL verification failed: {response.status_code}")
    except Exception as e:
        print(f"❌ URL verification error: {e}")
    
    # Test 3: Direct conversation
    print("\n3️⃣ Testing direct conversation...")
    try:
        conversation_data = {
            "message": "Hello! Can you help me with Python programming?"
        }
        response = requests.post(
            f"{base_url}/conversation",
            json=conversation_data,
            headers={"Content-Type": "application/json"}
        )
        
        if response.status_code == 200:
            data = response.json()
            if "response" in data:
                print("✅ Direct conversation working")
                print(f"   Response: {data['response'][:100]}...")
            else:
                print(f"❌ Direct conversation failed: {data}")
        else:
            print(f"❌ Direct conversation failed: {response.status_code}")
    except Exception as e:
        print(f"❌ Direct conversation error: {e}")
    
    # Test 4: Simple greeting filtering
    print("\n4️⃣ Testing simple greeting filtering...")
    try:
        greeting_data = {
            "message": "hi"
        }
        response = requests.post(
            f"{base_url}/conversation",
            json=greeting_data,
            headers={"Content-Type": "application/json"}
        )
        
        if response.status_code == 200:
            data = response.json()
            if data.get("status") == "filtered":
                print("✅ Simple greeting correctly filtered")
            else:
                print(f"❌ Simple greeting should have been filtered: {data}")
        else:
            print(f"❌ Simple greeting test failed: {response.status_code}")
    except Exception as e:
        print(f"❌ Simple greeting test error: {e}")
    
    print("\n🎉 API testing completed!")

def main():
    """Main function"""
    import sys
    
    if len(sys.argv) > 1:
        base_url = sys.argv[1]
    else:
        base_url = "http://localhost:8000"
    
    test_api(base_url)

if __name__ == "__main__":
    main()

```

### simple_api.py

```python
#!/usr/bin/env python3
"""
Slack Integration API with Two Workflows:

Workflow 1: Regular Messages (no @bot mention)
- Append message to user's chats block using memory_insert
- Format: "persona_name: message_text"

Workflow 2: Bot Mentions (@bot in message)
- Each agent summarizes their preferences from chats block
- Store summaries in summary blocks
- Planner analyzes conflicts and generates final itinerary
- Post final plan to Slack
"""

from fastapi import FastAPI, Request, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
import uvicorn
import json
import os
import re
import sys
from dotenv import load_dotenv
from slack_sdk import WebClient

# Load environment variables
load_dotenv()

# Add letta folder to path for imports
sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'letta'))

# Import workflow handlers
try:
    from user_message_handler import UserMessageHandler
    from bot_trigger_handler import BotTriggerHandler
except ImportError as e:
    print(f"⚠️  Workflow handler imports may fail: {e}")

# Initialize FastAPI app
app = FastAPI(
    title="Letta Agent Slack Integration API",
    description="Handles user messages and bot triggers for group trip planning",
    version="2.0.0"
)

# Add CORS middleware
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# Global Slack client
slack_client = None

# Message deduplication (track processed messages)
processed_messages = set()

# Bot user ID (from environment or hardcoded)
BOT_USER_ID = os.getenv("SLACK_BOT_USER_ID", "U09NE6B45GT")

# Simple greeting patterns to filter out
SIMPLE_GREETINGS = [
    r'^hi\s*$', r'^hey\s*$', r'^hiya\s*$',
    r'^good\s+(morning|afternoon|evening)\s*$',
    r'^what\'?s\s+up\s*$', r'^how\s+are\s+you\s*$',
    r'^thanks?\s*$', r'^thank\s+you\s*$',
    r'^ok\s*$', r'^okay\s*$', r'^sure\s*$',
    r'^yes\s*$', r'^no\s*$', r'^yep\s*$', r'^nope\s*$'
]

def is_simple_greeting(message: str) -> bool:
    """Check if message is a simple greeting that should be filtered out"""
    if not message or len(message.strip()) < 1:
        return True
    
    message_lower = message.lower().strip()
    
    # Check against simple greeting patterns
    for pattern in SIMPLE_GREETINGS:
        if re.match(pattern, message_lower):
            return True
    
    # Only filter out very short single-word responses
    words = message_lower.split()
    if len(words) == 1 and len(message_lower) < 3:
        return True
    
    return False

def has_bot_mention(text: str) -> bool:
    """Check if message mentions the bot (@bot or <@BOT_USER_ID>)"""
    if not text:
        return False
    return f"<@{BOT_USER_ID}>" in text or "@bot" in text.lower()

def create_message_id(user: str, text: str, ts: str) -> str:
    """Create unique ID for a message to prevent duplicates"""
    return f"{user}:{text}:{ts}"

def is_duplicate_message(user: str, text: str, ts: str) -> bool:
    """Check if we've already processed this message"""
    msg_id = create_message_id(user, text, ts)
    if msg_id in processed_messages:
        return True
    processed_messages.add(msg_id)
    return False

def log_request_info(request_type: str, user: str = None, message: str = None, should_process: bool = True):
    """Enhanced logging for requests"""
    if request_type == "slack_challenge":
        print(f"🔐 Slack URL verification challenge received")
    elif request_type == "slack_message":
        if should_process:
            print(f"📨 Slack message from {user}: {message[:80]}{'...' if len(message) > 80 else ''}")
        else:
            print(f"📨 Slack message from {user}: '{message}' (filtered - simple greeting)")
    elif request_type == "slack_app_mention":
        if should_process:
            print(f"📢 Slack app mention from {user}: {message[:80]}{'...' if len(message) > 80 else ''}")
        else:
            print(f"📢 Slack app mention from {user}: '{message}' (filtered - simple greeting)")
    elif request_type == "bot_trigger":
        print(f"🤖 Bot trigger from {user}: {message[:80]}{'...' if len(message) > 80 else ''}")
    else:
        print(f"📨 Received {request_type} event")

@app.on_event("startup")
async def startup_event():
    """Initialize Slack client on startup"""
    global slack_client
    try:
        # Initialize Slack client
        slack_token = os.getenv("SLACK_BOT_TOKEN")
        if slack_token:
            slack_client = WebClient(token=slack_token)
            print("✅ Slack client initialized successfully")
        else:
            print("⚠️  SLACK_BOT_TOKEN not found in environment variables")
    except Exception as e:
        print(f"❌ Error initializing services: {e}")
        raise

@app.post("/conversation")
async def conversation(request: Request):
    """
    Single endpoint that handles:
    1. Slack URL verification challenge
    2. Slack message events (ROUTE to Workflow 1 or 2 based on @bot mention)
    3. Slack app mention events (ROUTE to Workflow 2)
    
    Routing Logic:
    - If @bot mention: Trigger Workflow 2 (bot trigger → summary → conflict analysis → itinerary)
    - Else: Trigger Workflow 1 (append to user's chats block)
    """
    try:
        # Get the request body
        body = await request.json()
        
        # Handle Slack URL verification challenge
        if body.get("type") == "url_verification":
            challenge = body.get("challenge")
            if challenge:
                log_request_info("slack_challenge")
                print(f"✅ Slack URL verification successful")
                return {"challenge": challenge}
            else:
                raise HTTPException(status_code=400, detail="Missing challenge parameter")
        
        # Handle Slack event callbacks
        elif body.get("type") == "event_callback":
            event = body.get("event", {})
            event_type = event.
[truncated — 10900 more characters]
```

### letta/letta_ai.py

```python
from letta_client import Letta
import os
client = Letta(token=os.getenv("LETTA_API_KEY"))

response = client.agents.messages.create(
    agent_id=agent_state.id,
    messages=[
        {
            "role": "user",
            "content": "What do you know about me?"
        }
    ]
)
for message in response.messages:
    print(message)
```

[15 more indexed source files omitted to keep this export small. The full file list is in the Codebase structure section above.]