# Conversation System & Agent Loop Architecture
## Overview
The conversation system implements a complete conversational AI interface with tool calling capabilities, multi-conversation management, and persistence. It integrates with both inference providers (OpenRouter) and MCP (Model Context Protocol) servers to provide a unified tool ecosystem.
## System Architecture
### Core Components
```
┌─────────────────────────────────────────────────────────────┐
│ ConversationApp │
├─────────────────────┬───────────────────────────────────────┤
│ ConversationSidebar│ ChatInterface │
│ - Conversation List│ - MessageList │
│ - MCP Status │ - MessageInput │
│ - Management │ - Authentication │
└─────────────────────┴───────────────────────────────────────┘
```
### Context Providers
The system uses a layered context provider architecture:
```typescript
// Authentication & model management
// MCP server connections
// Conversation state & agent loops
```
## Data Models
### Conversation Structure
```typescript
interface Conversation {
id: string;
title: string;
messages: ConversationMessage[];
createdAt: Date;
updatedAt: Date;
status: 'idle' | 'thinking' | 'calling_tools' | 'error';
error?: string;
pendingToolCalls?: PendingToolCall[];
}
```
### Message Format
Messages use a structured content block system that supports text, tool use, and tool results:
```typescript
interface ConversationMessage {
id: string;
role: 'user' | 'assistant' | 'tool';
content: ConversationContentBlock[];
timestamp: Date;
}
type ConversationContentBlock =
| { type: 'text'; text: string }
| { type: 'tool_use'; id: string; name: string; input: Record }
| { type: 'tool_result'; tool_use_id: string; content: string | ContentItem[]; is_error?: boolean }
```
This design aligns with the Anthropic/Claude message format and supports rich tool interactions.
## Agent Loop Implementation
### Hook Architecture
The agent loop is implemented as a custom React hook (`useAgentLoop`) that provides:
```typescript
interface UseAgentLoopReturn {
executeAgentLoop: (conversation: Conversation, onUpdate: (conversation: Conversation) => void) => Promise;
stopLoop: (conversationId: string) => void;
getLoopState: (conversationId: string) => AgentLoopState | undefined;
}
```
### Execution Flow
1. **Conversation Processing**: Takes a full conversation and processes the latest user message
2. **Tool Discovery**: Aggregates tools from test tools and connected MCP servers
3. **Inference Request**: Sends messages + available tools to the inference provider
4. **Tool Execution**: Executes any tool calls returned by the model
5. **Result Integration**: Adds tool results back to the conversation
6. **Iteration**: Continues until no more tool calls or max iterations reached
```typescript
for (let iteration = 0; iteration < maxIterations; iteration++) {
// 1. Convert conversation to inference format
const inferenceMessages = toInferenceMessages(conversation.messages);
// 2. Get available tools (test + MCP)
const allTools = [...testTools, ...mcpTools];
// 3. Generate response with tools
const response = await currentProvider.generateResponse({
messages: inferenceMessages,
tools: allTools
});
// 4. Add assistant message to conversation
conversation.messages.push(fromInferenceResponse(response.message));
// 5. Execute tool calls if present
if (response.message.toolCalls) {
for (const toolCall of response.message.toolCalls) {
const result = await executeTool(toolCall);
conversation.messages.push(createToolResultMessage(result));
}
} else {
break; // No more tool calls, done
}
}
```
### Tool Integration
#### Test Tools
Built-in tools for development and testing:
- `get_weather`: Mock weather data
- `calculate`: Basic arithmetic evaluation
- `get_current_time`: Current timestamp with timezone support
#### MCP Tools
Tools from connected MCP servers are:
- **Name-prefixed**: `${serverName}__${toolName}` to avoid conflicts and comply with API requirements
- **Dynamically routed**: Tool calls extract server name and route to correct connection
- **Auto-discovered**: Available tools update when servers connect/disconnect
```typescript
// Tool execution routing
if (toolCall.function.name.includes('__')) {
const [serverName] = toolCall.function.name.split('__');
const connection = connections.find(conn => conn.name === serverName);
return await callMCPTool(connection.id, toolCall.function.name, toolCall.function.arguments);
}
```
#### Tool Naming Requirements
**Important**: Tool names must comply with OpenRouter API requirements:
- **Pattern**: Must match `^[a-zA-Z0-9_-]{1,64}$`
- **No dots allowed**: This is why we use double underscore (`__`) as separator
- **Length limit**: Maximum 64 characters total
Example tool names:
- ✅ `test__echo` (valid)
- ✅ `weather_service__get_forecast` (valid)
- ❌ `test.echo` (invalid - contains dot)
- ❌ `server__very_long_tool_name_that_exceeds_the_sixty_four_character_limit` (invalid - too long)
## State Management
### Conversation Persistence
Conversations are persisted to `localStorage` with automatic serialization/deserialization:
```typescript
// Save on changes
useEffect(() => {
if (hasLoadedPersisted.current && conversations.length > 0) {
localStorage.setItem('conversations', JSON.stringify(conversations));
}
}, [conversations]);
// Load on mount
useEffect(() => {
const persistedData = localStorage.getItem('conversations');
if (persistedData) {
const restored = JSON.parse(persistedData).map(conv => ({
...conv,
createdAt: new Date(conv.createdAt),
updatedAt: new Date(conv.updatedAt),
messages: conv.messages.map(msg => ({
...msg,
timestamp: new Date(msg.timestamp)
}))
}));
setConversations(restored);
}
}, []);
```
### Authentication State Management
The system uses a reactive authentication pattern to ensure UI consistency:
```typescript
// InferenceContext tracks auth state changes
const [authStateVersion, setAuthStateVersion] = useState(0);
const refreshAuthState = useCallback(() => {
setAuthStateVersion(prev => prev + 1);
}, []);
// Components use context's reactive isAuthenticated value
const contextValue = {
isAuthenticated: provider?.isAuthenticated || false,
refreshAuthState,
// ...
};
```
**Critical Pattern**: Always use `isAuthenticated` from context, not `provider?.isAuthenticated` directly, to ensure reactive updates.
## User Interface Components
### ConversationSidebar
- **Conversation list** with title, timestamp, status indicators
- **New conversation** creation
- **Conversation management** (delete, switch active)
- **MCP status toggle** for debugging
### ChatInterface
- **Authentication gate**: Shows inline login if not authenticated
- **Message display** with tool call visualization
- **Input handling** with disabled states
- **Real-time status** showing agent loop progress
### MessageList
- **Role-based styling** (user, assistant, tool)
- **Tool call blocks** showing tool name and input parameters
- **Tool result blocks** with success/error states and formatted output
- **Timestamp display** with relative formatting
### MessageInput
- **Auto-resize textarea** with Enter/Shift+Enter handling
- **Send button** with proper disabled states and tooltips
- **Character count** for long messages
## Authentication Flow
### Inline Authentication
Users authenticate directly in the chat interface without leaving the conversation view:
1. **Provider Selection**: Choose between API key and OAuth providers
2. **Authentication**: Enter API key or complete OAuth flow
3. **Model Loading**: Automatically load available models
4. **Context Update**: Refresh authentication state across all components
5. **Chat Enablement**: Message input becomes active
```typescript
// After successful authentication
await selectedProvider.authenticate({ type: 'api_key', apiKey });
await selectedProvider.loadModels();
setProvider(selectedProvider);
refreshAuthState(); // Critical: Force reactive update
```
## Development Guidelines
### Adding New Tools
1. **Test Tools**: Add to `testTools` array in `useAgentLoop.ts`
2. **MCP Tools**: Connect MCP server via MCP Provider Test tab
```typescript
const newTestTool: TestTool = {
type: 'function',
function: {
name: 'my_tool',
description: 'Description of what the tool does',
parameters: {
type: 'object',
properties: {
param1: { type: 'string', description: 'Parameter description' }
},
required: ['param1']
}
},
execute: async (args) => {
// Tool implementation
return { result: 'tool output' };
}
};
```
### Adding New Inference Providers
1. **Implement InferenceProvider abstract class**
2. **Add to available providers** in `InferenceLogin` component
3. **Handle authentication methods** (API key, OAuth)
### Message Format Extensions
To add new content block types:
1. **Extend ConversationContentBlock union type**
2. **Add rendering logic** in `MessageList.renderContentBlock()`
3. **Update conversion helpers** in `useAgentLoop`
### Debugging
Use the MCP Status panel (toggle in sidebar) to monitor:
- **Server connections** and their status
- **Available tools** from each server
- **Connection errors** and retry attempts
Debug conversation state in browser DevTools:
- **localStorage['conversations']**: Persisted conversation data
- **React DevTools**: Component state and context values
## Error Handling
### Agent Loop Errors
- **Tool execution failures**: Captured and shown as error tool results
- **Network failures**: Retry with exponential backoff
- **Authentication failures**: Graceful fallback to login screen
- **Max iterations**: Configurable limit to prevent infinite loops
### State Recovery
- **Conversation persistence**: Automatic save/restore across sessions
- **Connection restoration**: MCP servers automatically reconnect on app load
- **Authentication persistence**: Tokens stored securely in localStorage
## Performance Considerations
### Optimization Strategies
- **Message virtualization**: Consider for very long conversations
- **Tool result caching**: Avoid re-executing identical tool calls
- **Connection pooling**: Reuse MCP connections across conversations
- **Lazy loading**: Load conversation history on demand
### Memory Management
- **Conversation limits**: Consider archiving old conversations
- **Message trimming**: Truncate very long conversations for inference
- **Tool result size**: Limit large tool outputs in UI display
## Security Considerations
### Authentication
- **API keys**: Stored in localStorage, never sent to third parties
- **OAuth tokens**: Proper PKCE flow with secure token storage
- **MCP connections**: Support both authenticated and unauthenticated servers
### Tool Safety
- **Input validation**: Sanitize tool inputs before execution
- **Output sanitization**: Escape tool outputs in UI display
- **Execution limits**: Prevent long-running or resource-intensive tools
### Data Privacy
- **Local storage**: All conversation data stays on client
- **No analytics**: No conversation data sent to analytics services
- **MCP isolation**: Each server connection is isolated
## Future Enhancements
### Planned Features
- **Streaming responses**: Real-time message generation
- **File attachments**: Support for images and documents
- **Voice input/output**: Speech-to-text and text-to-speech
- **Advanced tool calling**: Parallel tool execution, tool composition
- **Conversation export**: Export conversations to various formats
- **Collaborative editing**: Shared conversations between users
### Architecture Improvements
- **Message streaming**: WebSocket-based real-time updates
- **Background processing**: Web Workers for tool execution
- **Offline support**: Service Worker for offline conversation access
- **Database migration**: Move from localStorage to IndexedDB
- **Plugin system**: Third-party tool and provider extensions
## Testing Strategy
### Unit Tests
- **Tool execution**: Verify tool calling and result handling
- **Message formatting**: Test content block serialization/deserialization
- **Authentication flows**: Mock provider authentication
### Integration Tests
- **Agent loop**: End-to-end conversation flow with tool calls
- **MCP integration**: Real MCP server connections and tool execution
- **Persistence**: Conversation save/restore functionality
### User Testing
- **Authentication UX**: Smooth login flow without friction
- **Tool usage**: Intuitive tool call visualization and results
- **Multi-conversation**: Easy conversation management and switching
## Troubleshooting Guide
### Common Issues
**"No authenticated inference provider available"**
- Verify `isAuthenticated` from context, not `provider?.isAuthenticated`
- Ensure `refreshAuthState()` called after authentication
- Check if provider is properly set in context
**Tool calls not working**
- Verify MCP server connections in MCP Status panel
- Check tool name prefixing for MCP tools (`server__tool_name`)
- Ensure tool schemas match expected format
- Verify tool names comply with API requirements (alphanumeric, underscore, hyphen only)
**Conversations not persisting**
- Check localStorage quota and permissions
- Verify conversation serialization doesn't fail
- Check browser's localStorage support
**UI not updating after authentication**
- Ensure components use context's `isAuthenticated` value
- Verify `refreshAuthState()` is called after auth changes
- Check React DevTools for proper context updates
**Agent loop hangs with MCP tools connected**
- Check browser console for OpenRouter API errors about tool names
- Verify tool names don't contain dots or other invalid characters
- Ensure tool names are under 64 characters total length
- Look for pattern validation errors: `String should match pattern '^[a-zA-Z0-9_-]{1,64}'`
This system provides a robust foundation for conversational AI with extensible tool calling capabilities, proper state management, and a clean user experience.