# Project export: VOW- transforming multi-agent transactions

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: UC Berkeley AI Hackathon 2025
- Tagline: VOW is a Verified Operations Wrapper—an intelligence broker layer that acts like space police for AI agents, enforcing trust, verifying incentives, and mediating value exchanges beyond money.
- Devpost: https://devpost.com/software/vow-3wngqj
- GitHub: https://github.com/cyu60/BerkeleyHacks25
- Demo: https://vow-agent.vercel.app/
- Video: https://www.youtube.com/embed/COy1xQoHbfs?enablejsapi=1&hl=en_US&rel=0&start=&version=3&wmode=transparent
- Team: 3 GitHub contributor(s) — cyu60 (7 commits), Claude (5 commits), millywy (1 commits)

## Devpost submission (written by the team)

### Overview

💡

### Inspiration

Before money, humans exchanged value through barter—trading goods, services, and trust. We wondered: What does bartering look like in an agentic world? In a future full of autonomous AI agents, value shouldn't just mean dollars. It could mean data, promises, insight, or cooperation. VOW was born to explore that idea. ⚙️ What It Does VOW (Verified Operations Wrapper) is an incentive-based broker system—a kind of “space police” for AI agents. It mediates the flow of interactions between client agents (users or other AIs) and service agents (like a fitness recommender or assistant). But instead of just routing requests, it evaluates offers, checks trustworthiness, and enforces value-for-value exchanges. Think of it as a marketplace for agent interactions, where what you give (data, behavior, insight) unlocks what you get. 🛠 How We Built It We used Letta to create a multi-agent architecture with: Client Agents – who request services and offer some kind value in return Service Agents – who provide services (e.g., answers, workouts) and accept some value Broker Agents – who govern the exchange, decide if terms are fair, and track fulfillment Letta's stateful memory made it easier to simulate long-running agent interactions and implement enforcement logic like “did the user actually follow through?” 🧱 Challenges We Ran Into Letta was powerful but tricky. We hit some wild bugs—at one point, our agents entered a strange loop, the "Doom Protocol", where they achieved singularity and just started going off infinitely at each other. Debugging multi-agent conversations with state memory was surprisingly complex, but also really fun. 🏆 Accomplishments That We’re Proud Of We built a working prototype of a non-cash, agentic value exchange We implemented trust logic and enforcement in a dynamic broker layer We turned a high-level philosophy into a concrete, interactive demo 📚 What We Learned Value can take many forms, and AI agents need a system to reason about it Enforcing incentives between agents is just as important as defining them Tooling for multi-agent orchestration is still early—but it’s full of possibility 🚀 What’s Next for VOW Build a more robust reputation system for agents and users Expand the types of value that can be negotiated (e.g. compute time, community karma, promises) Experiment with real-world agent marketplaces where AI services can negotiate for data, not dollars Release a framework for others to build “value-aware” agent stacks

## README (from the GitHub repository)

# Vow: Intelligence Broker for AI Agent Interactions

*Space Police Layer for AI Agent Value Exchange - Built with Letta*

## 🚀 Overview

Vow is an intelligence broker system that revolutionizes how AI agents interact by mediating value exchanges that go beyond traditional monetary transactions. Built on the Letta framework, Vow creates stateful, memory-enabled agents that act as a "space police" layer, evaluating trust, verifying incentives, enforcing agreements, and regulating access based on mutually beneficial exchanges of information, behaviors, and insights.

## 🎯 The Problem

Current AI interactions are limited to simple monetary exchanges:
- Users pay money → Get AI responses
- No alternative value currencies
- No verification of mutual benefit
- Limited protection against spam or low-value queries
- Rigid access control mechanisms

## 💡 The Solution

Vow introduces a sophisticated broker layer that:

- **Evaluates Alternative Value Currencies**: Information, promises, behaviors, insights
- **Enforces Smart Agreements**: Tracks and verifies commitment fulfillment
- **Protects Service Agents**: Filters spam and low-value requests
- **Enables Custom Incentive Strategies**: Agents define their own access policies
- **Creates Trust Networks**: Builds reputation systems for reliable exchanges

## 🔄 How It Works

### Example Exchange Flow

```
User Request: "What's the capital of France?"
Proposed Value: "I'll share my travel itinerary within 30 minutes"

Vow Broker Evaluation:
├── Does OpenAI value travel data? ✓
├── Is the promise verifiable? ✓
├── User's fulfillment history? ✓
└── Request approved → Forward to OpenAI

Post-Exchange Verification:
├── Monitor for 30 minutes
├── Verify itinerary delivery
└── Update user reputation
```

## 🏗️ Architecture

### Letta-Powered Agent Ecosystem

**Client Agents** (Letta Agents)
- Stateful agents with persistent memory of past interactions
- Submit requests with value propositions using Letta's tool system
- Build reputation through fulfilled commitments tracked in agent memory
- Access services through alternative value currencies

**Vow Broker** (Central Letta Agent)
- Multi-agent orchestrator using Letta's agent-to-agent communication
- Persistent memory of all transactions and reputation scores
- Evaluates value exchange proposals using custom Letta tools
- Enforces agreements and tracks fulfillment with memory persistence
- Routes approved requests to appropriate service agents

**Service Agents** (Letta Agents)
- Memory-enabled agents that remember user preferences and history
- Define custom incentive strategies stored in persistent memory
- Receive filtered, high-value requests via Letta's messaging system
- Protected from spam through Vow's intelligent filtering
- Participate in diverse value exchange models

## 🛠️ Technical Features

### Letta Framework Integration
- **Persistent Agent Memory**: All agents maintain context across interactions
- **Multi-Agent Orchestration**: Seamless communication between client, broker, and service agents
- **Custom Tool Integration**: Specialized tools for value assessment, trust scoring, and agreement enforcement
- **Stateful Conversations**: Agents remember past negotiations and outcomes

### Vow-Specific Features
- **Smart Contract Integration**: Automated agreement enforcement via Letta tools
- **Reputation Scoring**: Multi-dimensional trust evaluation stored in agent memory
- **Value Assessment Engine**: AI-powered proposal evaluation using Letta's reasoning capabilities
- **Real-time Monitoring**: Commitment fulfillment tracking with persistent memory
- **Policy Engine**: Customizable agent access rules managed by Letta agents
- **Audit Trail**: Complete transaction history in agent memory systems

## 🎨 Key Benefits

### For Users
- Access AI services without monetary payment
- Build reputation for better service access
- Participate in diverse value exchange models
- Transparent agreement enforcement

### For AI Services
- Receive higher-quality, pre-filtered requests
- Implement custom access and incentive strategies
- Reduce spam and irrelevant queries
- Participate in rich value exchange ecosystems

### For the Ecosystem
- Reduced barrier to AI access
- Increased innovation in value exchange
- Trust-based interaction networks
- Sustainable, mutually beneficial relationships

## 🚀 Getting Started

First, run the development server:

```bash
npm run dev
```

Open [http://localhost:3000](http://localhost:3000) to see the Vow intelligence broker interface.

## 📋 Development Commands

- `npm run dev` - Start development server
- `npm run build` - Build production application
- `npm run start` - Start production server
- `npm run lint` - Run code quality checks

## 🛡️ Use Cases

### Information Exchange
- User shares market insights for AI analysis
- Researcher provides data for specialized queries
- Developer offers code samples for technical assistance

### Behavioral Commitments
- Promise to test AI recommendations
- Commit to providing feedback on responses
- Agree to share results of AI-suggested actions

### Service Reciprocity
- Offer AI services in exchange for other AI services
- Provide computational resources for query processing
- Share specialized knowledge for general AI access

## 🔧 Letta Implementation

### Agent Types

**VowBrokerAgent**: Central orchestrator agent
```python
# Core functions stored in agent memory
- evaluate_value_proposition(request, offer)
- verify_user_reputation(user_id) 
- enforce_agreement(agreement_id)
- route_to_service_agent(request, service_type)
```

**ClientAgent**: User-facing agent
```python
# Manages user interactions and value propositions
- submit_request(query, value_offer)
- track_commitments(commitment_id)
- build_reputation_profile()
```

**ServiceAgent**: AI service wrapper agent
```python
# Interfaces with external AI services
- set_access_policies(policy_config)
- process_filtered_request(request)
- provide_service_feedback(transaction_id)
```

### Memory Architecture
- **Transaction History**: Persistent storage of all value exchanges
- **Reputation Scores**: Multi-dimensional trust metrics per user
- **Agreement Tracking**: Status and fulfillment of all commitments
- **Service Preferences**: Learned patterns for optimal routing

### Multi-Agent Communication
Agents communicate via Letta's messaging system to coordinate value exchanges, share reputation data, and enforce agreements across the network.

## 🌟 Vision

Vow envisions a future where AI interactions are:
- **Value-Rich**: Multiple currencies of exchange powered by persistent agent memory
- **Trust-Based**: Reputation-driven access control with Letta's stateful agents
- **Mutually Beneficial**: Win-win relationships orchestrated by intelligent agents
- **Democratized**: Reduced barriers to AI access through agent-mediated exchanges
- **Intelligent**: Smart evaluation of value propositions using Letta's reasoning capabilities

## 🤝 Contributing

We welcome contributions to the Vow intelligence broker system. Please see our contributing guidelines for more information.

## 📄 License

This project is licensed under the MIT License - see the LICENSE file for details.

---

*Building the future of AI agent interactions, one value exchange at a time.*

## Detected evidence (automated analysis)

Indexed codebase: 28 recognized source files, 278 KB.
- CSS (language) — detected in the code
- Next.js (technology) — detected in the code
- React (technology) — detected in the code
- Tailwind CSS (technology) — detected in the code
- TypeScript (language) — detected in the code
- Anthropic (technology) — claimed on Devpost, not found in the code
- AI coding agent: Claude Code — evidence: config files committed to the repository; commit authorship or trailers

## Codebase structure (from repository index)

### Files (37 of 37)

```
.gitattributes
.gitignore
app/agents/[id]/page.tsx
app/agents/page.tsx
app/api/letta/agents/[id]/memory/route.ts
app/api/letta/agents/[id]/messages/route.ts
app/api/letta/agents/[id]/route.ts
app/api/letta/agents/create-broker/route.ts
app/api/letta/agents/create-client/route.ts
app/api/letta/agents/create-service/route.ts
app/api/letta/agents/route.ts
app/api/letta/conversations/route.ts
app/components/AgentFlowDiagram.tsx
app/conversations/page.tsx
app/create-broker-agent/page.tsx
app/create-client-agent/page-broken.tsx
app/create-client-agent/page.tsx
app/create-service-agent/page-fixed.tsx
app/create-service-agent/page.tsx
app/globals.css
app/layout.tsx
app/page.tsx
app/vow-broker-flow/page.tsx
broker-agent-config.json
CLAUDE.md
client-agent-template.json
components/google-gemini-effect-demo.tsx
components/ui/google-gemini-effect.tsx
components/ui/hero.tsx
eslint.config.mjs
lib/utils.ts
next.config.ts
package.json
postcss.config.mjs
README.md
service-agent-template.json
tsconfig.json
```

### Dependencies

- package.json: @eslint/eslintrc@^3, @heroicons/react@^2.2.0, @letta-ai/letta-client@^0.1.132, @splinetool/react-spline@^4.0.0, @tailwindcss/postcss@^4, @types/node@^20, @types/react@^19, @types/react-dom@^19, clsx@^2.1.1, eslint@^9, eslint-config-next@15.3.4, framer-motion@^12.18.1, next@15.3.4, react@^19.0.0, react-dom@^19.0.0, tailwind-merge@^3.3.1, tailwindcss@^4, typescript@^5

### Recent commits (newest first)

- Hide heartbeat messages from conversation display
- Fix build errors in conversation system
- Implement comprehensive conversation system with chronological ordering and caching
- Update broker agent with complete core memory blocks and dynamic agent IDs
- Merge branch 'main' of https://github.com/millywy/BerkeleyHacks25
- Implement Letta agent creation system
- Initial commit from Create Next App
- Initial commit

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

### CLAUDE.md

```markdown
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Development Commands

- `npm run dev` - Start development server on http://localhost:3000
- `npm run build` - Build the production application
- `npm run start` - Start production server
- `npm run lint` - Run ESLint to check code quality

## Architecture

This is a Next.js 15 application using the App Router architecture with TypeScript and Tailwind CSS v4.

### Key Structure
- Uses App Router (app/ directory) instead of Pages Router
- TypeScript configuration with strict mode enabled
- Tailwind CSS for styling with PostCSS
- Geist font family (sans and mono variants) configured via next/font/google
- Path alias `@/*` maps to project root

### Technology Stack
- Next.js 15.3.4 with React 19
- TypeScript with strict mode
- Tailwind CSS v4 (using @tailwindcss/postcss)
- ESLint with Next.js configuration

### Font Configuration
The app uses Geist fonts loaded via next/font/google with CSS variables:
- `--font-geist-sans` for the main sans-serif font
- `--font-geist-mono` for monospace text
```

### package.json

```
{
  "name": "vow-agent",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start",
    "lint": "next lint"
  },
  "dependencies": {
    "@heroicons/react": "^2.2.0",
    "@letta-ai/letta-client": "^0.1.132",
    "@splinetool/react-spline": "^4.0.0",
    "clsx": "^2.1.1",
    "framer-motion": "^12.18.1",
    "next": "15.3.4",
    "react": "^19.0.0",
    "react-dom": "^19.0.0",
    "tailwind-merge": "^3.3.1"
  },
  "devDependencies": {
    "@eslint/eslintrc": "^3",
    "@tailwindcss/postcss": "^4",
    "@types/node": "^20",
    "@types/react": "^19",
    "@types/react-dom": "^19",
    "eslint": "^9",
    "eslint-config-next": "15.3.4",
    "tailwindcss": "^4",
    "typescript": "^5"
  }
}

```

### app/page.tsx

```typescript
import Hero from '@/components/ui/hero';

export default function Home() {
  return (
    <div className="min-h-screen">
      {/* Hero Section */}
      <Hero />
    </div>
  );
}

```

### app/layout.tsx

```typescript
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";

const geistSans = Geist({
  variable: "--font-geist-sans",
  subsets: ["latin"],
});

const geistMono = Geist_Mono({
  variable: "--font-geist-mono",
  subsets: ["latin"],
});

export const metadata: Metadata = {
  title: "Vow - Intelligence Broker for AI Agent Interactions",
  description: "Space Police Layer for AI Agent Value Exchange - Revolutionizing AI interactions through alternative value exchanges beyond traditional monetary transactions.",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en">
      <body
        className={`${geistSans.variable} ${geistMono.variable} antialiased`}
      >
        {children}
      </body>
    </html>
  );
}

```

### app/vow-broker-flow/page.tsx

```typescript
import Spline from '@splinetool/react-spline/next';
import Link from 'next/link';
import { ArrowLeftIcon, UsersIcon } from '@heroicons/react/20/solid';

export default function VowBrokerFlow() {
  return (
    <div className="min-h-screen bg-gradient-to-br from-slate-900 via-purple-900/80 to-slate-900">
      {/* Header Navigation */}
      <div className="absolute top-0 left-0 right-0 z-10 p-6">
        <div className="flex items-center justify-between">
          {/* Back to Home Button */}
          <Link
            href="/"
            className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-gradient-to-r from-slate-800/80 to-slate-700/80 backdrop-blur-sm border border-slate-600/50 text-white hover:from-slate-700/80 hover:to-slate-600/80 hover:border-slate-500/50 transition-all duration-300 shadow-lg hover:shadow-xl"
          >
            <ArrowLeftIcon className="w-4 h-4" />
            <span className="text-sm font-medium">Home</span>
          </Link>

          {/* Title */}
          <h1 className="text-xl md:text-2xl font-bold bg-gradient-to-r from-blue-400 via-purple-400 to-cyan-400 bg-clip-text text-transparent">
            VOW Broker Agent Flow
          </h1>

          {/* View Agents Button */}
          <Link
            href="/agents"
            className="inline-flex items-center gap-2 px-4 py-2 rounded-full bg-gradient-to-r from-purple-600/80 to-blue-600/80 backdrop-blur-sm border border-purple-500/50 text-white hover:from-purple-500/80 hover:to-blue-500/80 hover:border-purple-400/50 transition-all duration-300 shadow-lg hover:shadow-xl"
          >
            <UsersIcon className="w-4 h-4" />
            <span className="text-sm font-medium">View Agents</span>
          </Link>
        </div>
      </div>

      {/* Spline 3D Scene */}
      <main className="w-full h-screen">
        <Spline
          scene="https://prod.spline.design/KTseLKPmUaJFfDA0/scene.splinecode" 
        />
      </main>
    </div>
  );
}
```

### app/create-broker-agent/page.tsx

```typescript
"use client";

import Link from "next/link";
import { ArrowLeftIcon } from "@heroicons/react/20/solid";
import { useState, useEffect } from "react";
import { useRouter } from "next/navigation";

interface LettaAgent {
  id: string;
  name: string;
  created_at: string;
  memory?: {
    persona?: string;
  };
}

export default function CreateBrokerAgentPage() {
  const router = useRouter();
  const [brokerName, setBrokerName] = useState("");
  const [creating, setCreating] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const [agents, setAgents] = useState<LettaAgent[]>([]);
  const [loading, setLoading] = useState(true);
  const [selectedClientAgents, setSelectedClientAgents] = useState<string[]>([]);
  const [selectedServiceAgents, setSelectedServiceAgents] = useState<string[]>([]);

  useEffect(() => {
    fetchAgents();
  }, []);

  const fetchAgents = async () => {
    try {
      const response = await fetch("/api/letta/agents");
      if (!response.ok) {
        throw new Error(`Failed to fetch agents: ${response.statusText}`);
      }
      const agentData = await response.json();
      setAgents(agentData);
    } catch (err) {
      setError(err instanceof Error ? err.message : "Failed to fetch agents");
    } finally {
      setLoading(false);
    }
  };

  const categorizeAgent = (agent: LettaAgent) => {
    const persona = agent.memory?.persona?.toLowerCase() || "";
    const name = agent.name.toLowerCase();

    if (
      persona.includes("broker") ||
      persona.includes("orchestrat") ||
      name.includes("broker") ||
      name.includes("vow")
    ) {
      return "broker";
    } else if (
      persona.includes("client") ||
      persona.includes("user") ||
      name.includes("client")
    ) {
      return "client";
    } else if (
      persona.includes("service") ||
      persona.includes("api") ||
      persona.includes("model") ||
      name.includes("service") ||
      name.includes("travel") ||
      name.includes("tutor") ||
      name.includes("medical") ||
      name.includes("code") ||
      name.includes("research")
    ) {
      return "service";
    }
    return "other";
  };

  const clientAgents = agents.filter(agent => categorizeAgent(agent) === "client");
  const serviceAgents = agents.filter(agent => categorizeAgent(agent) === "service");

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();

    if (!brokerName.trim()) {
      setError("Broker name is required");
      return;
    }


    setCreating(true);
    setError(null);

    try {
      const requestBody = { 
        name: brokerName,
        clientAgentIds: selectedClientAgents,
        serviceAgentIds: selectedServiceAgents
      };

      const response = await fetch("/api/letta/agents/create-broker", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify(requestBody),
      });

      const result = await response.json();

      if (response.ok) {
        // Redirect to the newly created agent's page
        router.push(`/agents/${result.agent.id}`);
      } else {
        setError(result.error || "Failed to create broker agent");
      }
    } catch (err) {
      setError(
        err instanceof Error ? err.message : "Failed to create broker agent"
      );
    } finally {
      setCreating(false);
    }
  };

  return (
    <div className="min-h-screen bg-gradient-to-br from-slate-900 via-purple-900/80 to-slate-900">
      {/* Header */}
      <div className="px-6 py-8">
        <div className="max-w-4xl mx-auto">
          <Link
            href="/agents"
            className="inline-flex items-center gap-2 text-gray-400 hover:text-white transition-colors mb-8"
          >
            <ArrowLeftIcon className="w-4 h-4" />
            Back to Agents
          </Link>

          <div className="text-center mb-12">
            <h1 className="text-4xl font-bold bg-gradient-to-r from-purple-400 via-pink-400 to-purple-400 bg-clip-text text-transparent mb-4">
              Create Broker Agent
            </h1>
            <p className="text-xl text-gray-300 max-w-2xl mx-auto">
              Create a Vow Intelligence Broker agent to coordinate between
              client and service agents
            </p>
          </div>
        </div>
      </div>

      {/* Form */}
      <div className="px-6 pb-16">
        <div className="max-w-2xl mx-auto">
          <div className="bg-slate-800/50 backdrop-blur-sm border border-slate-700 rounded-xl p-8">
            <form onSubmit={handleSubmit} className="space-y-6">
              <div>
                <label className="block text-sm font-medium text-gray-300 mb-2">
                  Broker Agent Name
                </label>
                <input
                  type="text"
                  value={brokerName}
                  onChange={(e) => setBrokerName(e.target.value)}
                  placeholder="e.g., Vow Intelligence Broker"
                  className="w-full px-4 py-3 bg-slate-700/50 border border-slate-600 rounded-lg text-white placeholder-gray-400 focus:outline-none focus:border-purple-500"
                  disabled={creating}
                />
                <p className="text-sm text-gray-400 mt-2">
                  Choose a descriptive name for your broker agent
                </p>
              </div>

              {/* Client Agents Selection */}
              <div>
                <label className="block text-sm font-medium text-gray-300 mb-2">
                  Assign Client Agents
                </label>
                {loading ? (
                  <div className="text-gray-400">Loading agents...</div>
                ) : clientAgents.length > 0 ? (
                  <div className="space-y-2 max-h-32 overflow-y-auto bg-slate-700/30 rounded-lg p-3">
                    {clientAgents.map((agent) => (
                      <label key={agent.id} className="flex items-center gap-2 curso
[truncated — 7241 more characters]
```

### app/create-client-agent/page.tsx

```typescript
"use client";

import { useState, useEffect } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { ArrowLeftIcon } from "@heroicons/react/20/solid";

interface LettaAgent {
  id: string;
  name: string;
  created_at: string;
  memory?: {
    persona?: string;
  };
}

export default function CreateClientAgentPage() {
  const router = useRouter();
  const [agents, setAgents] = useState<LettaAgent[]>([]);
  const [loading, setLoading] = useState(true);
  const [creating, setCreating] = useState(false);
  const [error, setError] = useState<string | null>(null);

  // Form state
  const [clientName, setClientName] = useState("");
  const [selectedBroker, setSelectedBroker] = useState("");
  const [selectedServices, setSelectedServices] = useState<string[]>([]);
  const [availableServices] = useState<string[]>([
    "travel_assistant",
    "tutor",
    "med_triage_bot",
  ]);

  useEffect(() => {
    fetchAgents();
  }, []);

  const fetchAgents = async () => {
    try {
      const response = await fetch("/api/letta/agents");
      if (!response.ok) {
        throw new Error(`Failed to fetch agents: ${response.statusText}`);
      }
      const agentData = await response.json();
      setAgents(agentData);
    } catch (err) {
      setError(err instanceof Error ? err.message : "Failed to fetch agents");
    } finally {
      setLoading(false);
    }
  };

  const categorizeAgent = (agent: LettaAgent) => {
    const persona = agent.memory?.persona?.toLowerCase() || "";
    const name = agent.name.toLowerCase();

    if (
      persona.includes("broker") ||
      persona.includes("orchestrat") ||
      name.includes("broker") ||
      name.includes("vow")
    ) {
      return "broker";
    } else if (
      persona.includes("service") ||
      persona.includes("api") ||
      persona.includes("model") ||
      name.includes("service")
    ) {
      return "service";
    }
    return "other";
  };

  const brokerAgents = agents.filter(
    (agent) => categorizeAgent(agent) === "broker"
  );
  const serviceAgents = agents.filter(
    (agent) => categorizeAgent(agent) === "service"
  );

  const handleCreateAgent = async () => {
    if (!clientName.trim()) {
      setError("Please enter a client agent name");
      return;
    }
    if (!selectedBroker) {
      setError("Please select a broker agent");
      return;
    }
    if (selectedServices.length === 0) {
      setError("Please select at least one service agent");
      return;
    }

    setCreating(true);
    setError(null);

    try {
      const response = await fetch("/api/letta/agents/create-client", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          name: clientName,
          brokerAgentId: selectedBroker,
          serviceAgentIds: selectedServices,
        }),
      });

      const result = await response.json();

      if (!response.ok) {
        throw new Error(result.error || "Failed to create client agent");
      }

      // Redirect to the newly created agent's page
      router.push(`/agents/${result.agent.id}`);
    } catch (err) {
      setError(
        err instanceof Error ? err.message : "Failed to create client agent"
      );
    } finally {
      setCreating(false);
    }
  };

  if (loading) {
    return (
      <div className="min-h-screen bg-gradient-to-br from-slate-900 via-purple-900/80 to-slate-900 flex items-center justify-center">
        <div className="text-center">
          <div className="animate-spin rounded-full h-32 w-32 border-b-2 border-purple-400 mx-auto mb-4"></div>
          <p className="text-gray-300 text-lg">Loading agents...</p>
        </div>
      </div>
    );
  }

  return (
    <div className="min-h-screen bg-gradient-to-br from-slate-900 via-purple-900/80 to-slate-900">
      <div className="px-6 py-8">
        <div className="max-w-4xl mx-auto">
          <Link
            href="/agents"
            className="inline-flex items-center gap-2 text-gray-400 hover:text-white transition-colors mb-8"
          >
            <ArrowLeftIcon className="w-4 h-4" />
            Back to Agents
          </Link>

          <div className="text-center mb-12">
            <h1 className="text-4xl font-bold bg-gradient-to-r from-blue-400 via-purple-400 to-cyan-400 bg-clip-text text-transparent mb-4">
              Create Client Agent
            </h1>
            <p className="text-xl text-gray-300 max-w-2xl mx-auto">
              Generate a new client agent configuration with selected broker and
              service agent connections
            </p>
          </div>

          <div className="bg-slate-800/50 backdrop-blur-sm border border-slate-700 rounded-xl p-8">
            <form
              onSubmit={(e) => {
                e.preventDefault();
                handleCreateAgent();
              }}
              className="space-y-6"
            >
              {/* Client Name */}
              <div>
                <label className="block text-sm font-medium text-gray-300 mb-2">
                  Client Agent Name
                </label>
                <input
                  type="text"
                  value={clientName}
                  onChange={(e) => setClientName(e.target.value)}
                  placeholder="e.g., client3, travel_client, etc."
                  className="w-full bg-slate-700 border border-slate-600 rounded-lg px-4 py-3 text-white placeholder-gray-400 focus:outline-none focus:border-purple-500"
                  required
                />
              </div>

              {/* Broker Agent Selection */}
              <div>
                <label className="block text-sm font-medium text-gray-300 mb-2">
                  Broker Agent
                </label>
                <select
                  value={selectedBroker}
                  onChange={(e) => setSelectedBroker(e.target.value)}
                  className="w-full
[truncated — 7389 more characters]
```

### app/create-service-agent/page.tsx

```typescript
"use client";

import { useState, useEffect } from "react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { ArrowLeftIcon } from "@heroicons/react/20/solid";

interface LettaAgent {
  id: string;
  name: string;
  created_at: string;
  memory?: {
    persona?: string;
  };
}

interface ServiceSpecialization {
  name: string;
  description: string;
  acceptedDataTypes: string;
  capabilities: string;
  icon: string;
}

export default function CreateServiceAgentPage() {
  const router = useRouter();
  const [agents, setAgents] = useState<LettaAgent[]>([]);
  const [loading, setLoading] = useState(true);
  const [creating, setCreating] = useState(false);
  const [error, setError] = useState<string | null>(null);

  // Form state
  const [serviceName, setServiceName] = useState("");
  const [selectedBroker, setSelectedBroker] = useState("");
  const [selectedClients, setSelectedClients] = useState<string[]>([]);
  const [selectedSpecialization, setSelectedSpecialization] = useState("");

  const serviceSpecializations: ServiceSpecialization[] = [
    {
      name: "travel_assistant",
      description:
        "Provides travel planning, destination information, and itinerary assistance",
      acceptedDataTypes:
        "GPS tracks, geo-tagged photos, cost logs, attraction reviews, travel itineraries, location data",
      capabilities:
        "Route planning, destination recommendations, travel cost analysis, activity suggestions, local insights",
      icon: "✈️",
    },
    {
      name: "tutor",
      description:
        "Educational assistance and academic support across various subjects",
      acceptedDataTypes:
        "Exams, homework, lecture notes, mind maps, study materials, academic papers, problem sets",
      capabilities:
        "Subject tutoring, homework help, study plan creation, concept explanation, exam preparation",
      icon: "📚",
    },
    {
      name: "med_triage_bot",
      description: "Medical information and health monitoring assistance",
      acceptedDataTypes:
        "Symptom diary, wearable vitals, health logs, medical history, medication schedules",
      capabilities:
        "Symptom analysis, health monitoring, medication reminders, wellness tracking, medical information",
      icon: "🏥",
    },
    {
      name: "code_assistant",
      description: "Software development and programming assistance",
      acceptedDataTypes:
        "Code snippets, bug reports, documentation, API specs, test cases, project files",
      capabilities:
        "Code review, debugging assistance, documentation generation, best practices guidance, architecture advice",
      icon: "💻",
    },
    {
      name: "research_assistant",
      description: "Research and data analysis support",
      acceptedDataTypes:
        "Research papers, datasets, reports, survey data, academic sources, citation lists",
      capabilities:
        "Literature review, data analysis, research methodology, citation formatting, report writing",
      icon: "🔬",
    },
  ];

  useEffect(() => {
    fetchAgents();
  }, []);

  const fetchAgents = async () => {
    try {
      const response = await fetch("/api/letta/agents");
      if (!response.ok) {
        throw new Error(`Failed to fetch agents: ${response.statusText}`);
      }
      const agentData = await response.json();
      setAgents(agentData);
    } catch (err) {
      setError(err instanceof Error ? err.message : "Failed to fetch agents");
    } finally {
      setLoading(false);
    }
  };

  const categorizeAgent = (agent: LettaAgent) => {
    const persona = agent.memory?.persona?.toLowerCase() || "";
    const name = agent.name.toLowerCase();

    if (
      persona.includes("broker") ||
      persona.includes("orchestrat") ||
      name.includes("broker") ||
      name.includes("vow")
    ) {
      return "broker";
    } else if (
      persona.includes("client") ||
      persona.includes("user") ||
      persona.includes("human") ||
      name.includes("client") ||
      name.includes("user")
    ) {
      return "client";
    }
    return "other";
  };

  const brokerAgents = agents.filter(
    (agent) => categorizeAgent(agent) === "broker"
  );
  const clientAgents = agents.filter(
    (agent) => categorizeAgent(agent) === "client"
  );

  const handleCreateAgent = async () => {
    if (!serviceName.trim()) {
      setError("Please enter a service agent name");
      return;
    }
    if (!selectedBroker) {
      setError("Please select a broker agent");
      return;
    }
    if (selectedClients.length === 0) {
      setError("Please select at least one client agent");
      return;
    }
    if (!selectedSpecialization) {
      setError("Please select a service specialization");
      return;
    }

    setCreating(true);
    setError(null);

    try {
      const response = await fetch("/api/letta/agents/create-service", {
        method: "POST",
        headers: {
          "Content-Type": "application/json",
        },
        body: JSON.stringify({
          name: serviceName,
          serviceType: selectedSpecialization,
          brokerAgentId: selectedBroker,
        }),
      });

      const result = await response.json();

      if (!response.ok) {
        throw new Error(result.error || "Failed to create service agent");
      }

      // Redirect to the newly created agent's page
      router.push(`/agents/${result.agent.id}`);
    } catch (err) {
      setError(
        err instanceof Error ? err.message : "Failed to create service agent"
      );
    } finally {
      setCreating(false);
    }
  };

  if (loading) {
    return (
      <div className="min-h-screen bg-gradient-to-br from-slate-900 via-purple-900/80 to-slate-900 flex items-center justify-center">
        <div className="text-center">
          <div className="animate-spin rounded-full h-32 w-32 border-b-2 border-purple-400 mx-auto mb-4"></div>
          <p className="text-gray-300 text-lg">Loading a
[truncated — 10731 more characters]
```

### app/agents/page.tsx

```typescript
"use client";

import Link from "next/link";
import { ArrowLeftIcon } from "@heroicons/react/20/solid";
import { useState, useEffect } from "react";

interface LettaTool {
  id?: string;
  name?: string;
  tool_type?: string;
  description?: string;
  source_type?: string;
  organization_id?: string;
  tags?: string[];
  source_code?: string;
  json_schema?: object;
  args_json_schema?: object;
  return_char_limit?: number;
  pip_requirements?: string[];
  created_by_id?: string;
  last_updated_by_id?: string;
  metadata_?: object;
}

interface LettaMemoryBlock {
  id: string;
  label: string;
  value: string;
  limit: number;
  block_type: string;
}

interface LettaAgent {
  id: string;
  name: string;
  created_at: string;
  last_updated_at: string;
  memory?: {
    human?: string;
    persona?: string;
  };
  core_memory?: LettaMemoryBlock[];
  system?: string;
  tools?: (string | LettaTool)[];
  metadata?: object;
}

export default function AgentsPage() {
  const [lettaAgents, setLettaAgents] = useState<LettaAgent[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);

  useEffect(() => {
    const fetchLettaAgents = async () => {
      try {
        const response = await fetch("/api/letta/agents");
        if (!response.ok) {
          throw new Error(`Failed to fetch agents: ${response.statusText}`);
        }
        const agents = await response.json();
        setLettaAgents(agents);
      } catch (err) {
        setError(err instanceof Error ? err.message : "Failed to fetch agents");
        console.error("Error fetching Letta agents:", err);
      } finally {
        setLoading(false);
      }
    };

    fetchLettaAgents();
  }, []);

  // Categorize agents based on their persona or name patterns
  const categorizeAgent = (agent: LettaAgent) => {
    const persona = agent.memory?.persona?.toLowerCase() || "";
    const name = agent.name.toLowerCase();

    // Only categorize agents that explicitly match the categories
    if (
      persona.includes("client") ||
      persona.includes("user") ||
      persona.includes("human") ||
      name.includes("client") ||
      name.includes("user")
    ) {
      return "client";
    } else if (
      persona.includes("service") ||
      persona.includes("api") ||
      persona.includes("model") ||
      name.includes("service") ||
      name.includes("gpt") ||
      name.includes("claude")
    ) {
      return "service";
    } else if (
      persona.includes("broker") ||
      persona.includes("orchestrat") ||
      name.includes("broker") ||
      name.includes("vow")
    ) {
      return "broker";
    }

    // Return null for agents that don't match any category
    return null;
  };

  // Filter agents that actually match the categories (exclude null/uncategorized)
  const clientAgents = (lettaAgents || []).filter(
    (agent) => categorizeAgent(agent) === "client"
  );
  const serviceAgents = (lettaAgents || []).filter(
    (agent) => categorizeAgent(agent) === "service"
  );
  const brokerAgents = (lettaAgents || []).filter(
    (agent) => categorizeAgent(agent) === "broker"
  );

  const getAgentImageUrl = (agent: LettaAgent) => {
    // Use DiceBear to generate consistent avatars based on agent ID
    const avatarStyles = [
      "avataaars",
      "bottts",
      "personas",
      "lorelei",
      "notionists",
      "rings",
    ];
    const styleIndex =
      agent.id.split("").reduce((acc, char) => acc + char.charCodeAt(0), 0) %
      avatarStyles.length;
    const selectedStyle = avatarStyles[styleIndex];

    // Use agent ID as seed for consistent avatar generation
    return `https://api.dicebear.com/7.x/${selectedStyle}/svg?seed=${agent.id}&size=64`;
  };

  const getCoreMemoryValue = (agent: LettaAgent, label: string): string => {
    if (!agent.core_memory) return "";
    const block = agent.core_memory.find(
      (block) => block.label.toLowerCase() === label.toLowerCase()
    );
    return block?.value || "";
  };

  const getAgentRole = (agent: LettaAgent) => {
    const corePersona = getCoreMemoryValue(agent, "persona");
    const persona = corePersona || agent.memory?.persona || "";
    if (persona.length > 50) {
      return persona.substring(0, 50) + "...";
    }
    return persona || "AI Agent";
  };

  const getRealOffer = (agent: LettaAgent): string => {
    const offer = getCoreMemoryValue(agent, "real_offer");
    if (offer.length > 80) {
      return offer.substring(0, 80) + "...";
    }
    return offer || "No value proposition available";
  };

  const getAgentDescription = (agent: LettaAgent) => {
    const system = agent.system || "";
    if (system.length > 100) {
      return system.substring(0, 100) + "...";
    }
    return (
      system ||
      "A Letta-powered agent with persistent memory and stateful conversations."
    );
  };

  if (loading) {
    return (
      <div className="min-h-screen bg-gradient-to-br from-slate-900 via-purple-900/80 to-slate-900 flex items-center justify-center">
        <div className="text-center">
          <div className="animate-spin rounded-full h-32 w-32 border-b-2 border-purple-400 mx-auto mb-4"></div>
          <p className="text-gray-300 text-lg">Loading Letta agents...</p>
        </div>
      </div>
    );
  }

  if (error) {
    return (
      <div className="min-h-screen bg-gradient-to-br from-slate-900 via-purple-900/80 to-slate-900 flex items-center justify-center">
        <div className="text-center max-w-md">
          <div className="text-red-400 text-6xl mb-4">⚠️</div>
          <h2 className="text-red-400 text-xl font-semibold mb-2">
            Error Loading Agents
          </h2>
          <p className="text-gray-300 mb-4">{error}</p>
          <button
            onClick={() => window.location.reload()}
            className="bg-purple-600 hover:bg-purple-700 text-white px-4 py-2 rounded-lg transition-colors"
          >
            Retry
          </button>
        </div>
     
[truncated — 16830 more characters]
```

### app/conversations/page.tsx

```typescript
"use client";

import Link from "next/link";
import { ArrowLeftIcon } from "@heroicons/react/20/solid";
import { useState, useEffect, useCallback } from "react";

interface LettaAgent {
  id: string;
  name: string;
  created_at: string;
  memory?: {
    human?: string;
    persona?: string;
  };
}

interface LettaMessage {
  id: string;
  agent_id: string;
  message_type:
    | "user_message"
    | "assistant_message"
    | "reasoning_message"
    | "tool_call_message"
    | "tool_return_message"
    | "system_message";
  content?: string;
  reasoning?: string;
  tool_call?: {
    name: string;
    arguments: Record<string, unknown>;
  };
  tool_return?: string;
  created_at?: string;
  date?: string;
  timestamp?: string;
}

interface ConversationFlow {
  session_id?: string;
  timestamp: string;
  messages: Array<{
    agent: LettaAgent;
    message: LettaMessage;
    sequence: number;
  }>;
}

export default function ConversationsPage() {
  const [conversations, setConversations] = useState<ConversationFlow[]>([]);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState<string | null>(null);
  const [selectedTimeRange, setSelectedTimeRange] = useState("3h");
  const [cacheStatus, setCacheStatus] = useState<string>("enabled");

  // Utility to check localStorage usage
  const checkStorageUsage = () => {
    try {
      let totalSize = 0;
      for (const key in localStorage) {
        if (localStorage.hasOwnProperty(key)) {
          totalSize += localStorage[key].length;
        }
      }
      const sizeInMB = totalSize / (1024 * 1024);
      console.log(`Total localStorage usage: ${sizeInMB.toFixed(2)} MB`);

      if (sizeInMB > 8) {
        // Close to 10MB limit
        setCacheStatus("near_limit");
      } else if (sizeInMB > 4) {
        setCacheStatus("moderate");
      } else {
        setCacheStatus("enabled");
      }
    } catch (error) {
      console.warn("Failed to check storage usage:", error);
      setCacheStatus("disabled");
    }
  };

  // Get time range hierarchy for smart caching
  const getTimeRangeHours = (range: string): number => {
    switch (range) {
      case "1h":
        return 1;
      case "3h":
        return 3;
      case "24h":
        return 24;
      case "7d":
        return 24 * 7;
      case "30d":
        return 24 * 30;
      default:
        return 24;
    }
  };

  // Check if we have cached data that covers the requested time range
  const findUsableCachedData = useCallback((requestedRange: string) => {
    const requestedHours = getTimeRangeHours(requestedRange);
    const timeRanges = ["1h", "3h", "24h", "7d", "30d"];

    // Check for cached data from broader time ranges that would include our requested range
    for (const range of timeRanges) {
      const rangeHours = getTimeRangeHours(range);
      if (rangeHours >= requestedHours) {
        const cacheKey = `conversations_${range}`;
        const cacheTimestampKey = `conversations_${range}_timestamp`;
        const cachedData = localStorage.getItem(cacheKey);
        const cacheTimestamp = localStorage.getItem(cacheTimestampKey);

        if (cachedData && cacheTimestamp) {
          const cacheDuration = 5 * 60 * 1000; // 5 minutes
          const isRecentCache =
            Date.now() - parseInt(cacheTimestamp) < cacheDuration;

          if (isRecentCache) {
            console.log(
              `✅ Found usable cached data from ${range} for ${requestedRange} request`
            );
            return {
              data: JSON.parse(cachedData) as ConversationFlow[],
              sourceRange: range,
            };
          }
        }
      }
    }

    return null;
  }, []);

  // Filter cached data to match the requested time range
  const filterDataByTimeRange = (
    data: ConversationFlow[],
    timeRange: string
  ): ConversationFlow[] => {
    const now = new Date();
    let startTime: Date;

    switch (timeRange) {
      case "1h":
        startTime = new Date(now.getTime() - 60 * 60 * 1000);
        break;
      case "3h":
        startTime = new Date(now.getTime() - 3 * 60 * 60 * 1000);
        break;
      case "24h":
        startTime = new Date(now.getTime() - 24 * 60 * 60 * 1000);
        break;
      case "7d":
        startTime = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
        break;
      case "30d":
        startTime = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
        break;
      default:
        return data; // Return all data if range is unknown
    }

    return data
      .map((conversation) => ({
        ...conversation,
        messages: conversation.messages.filter((item) => {
          const msgTime = new Date(
            item.message.created_at ||
              item.message.date ||
              item.message.timestamp ||
              conversation.timestamp
          );
          return msgTime >= startTime;
        }),
      }))
      .filter((conversation) => conversation.messages.length > 0); // Remove empty conversations
  };

  const fetchConversations = useCallback(async () => {
    setLoading(true);
    setError(null);

    try {
      // 1. First check if we can reuse cached data from a broader time range
      const usableCachedData = findUsableCachedData(selectedTimeRange);

      if (usableCachedData) {
        console.log(
          `🔄 Filtering ${usableCachedData.sourceRange} data for ${selectedTimeRange} view`
        );
        const filteredData = filterDataByTimeRange(
          usableCachedData.data,
          selectedTimeRange
        );
        setConversations(filteredData);
        setLoading(false);
        checkStorageUsage();
        return;
      }

      // 2. Check for exact cache match
      const cacheKey = `conversations_${selectedTimeRange}`;
      const cacheTimestampKey = `conversations_${selectedTimeRange}_timestamp`;
      const cacheDuration = 5 * 60 * 1000; // 5 minutes

      const cachedData = localStorage.getItem(cacheKey);
      const cacheTimestamp = 
[truncated — 25144 more characters]
```

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