Add inital implementation of an agent

Change-Id: Ib60c33e8c1a44bc9341cac5c1f1fdc518fb5ed1e
diff --git a/server/agent/README.md b/server/agent/README.md
new file mode 100644
index 0000000..0015d90
--- /dev/null
+++ b/server/agent/README.md
@@ -0,0 +1,298 @@
+# Agent Package
+
+The `agent` package provides an AI agent system that can autonomously process tasks using LLM services, manage tasks through a task management system, and create pull requests with solutions.
+
+## Overview
+
+The agent system consists of:
+
+- **AI Agent**: Processes tasks using LLM services
+- **Task Manager**: Manages task lifecycle and assignment
+- **Git Integration**: Creates pull requests with solutions
+- **Infinite Loop**: Continuously processes assigned tasks
+
+## Features
+
+- **Autonomous Task Processing**: Agents automatically pick up and process assigned tasks
+- **LLM Integration**: Uses configurable LLM providers (OpenAI, Claude, etc.)
+- **Task Management**: Integrates with task management systems
+- **Git Operations**: Creates branches and pull requests for solutions
+- **Configurable Roles**: Different agents can have different roles and system prompts
+- **Error Handling**: Robust error handling with graceful recovery
+
+## Quick Start
+
+### 1. Basic Setup
+
+```go
+package main
+
+import (
+    "log"
+    "time"
+    
+    "github.com/iomodo/staff/agent"
+    "github.com/iomodo/staff/git"
+    "github.com/iomodo/staff/llm"
+    "github.com/iomodo/staff/tm"
+    "github.com/iomodo/staff/tm/git_tm"
+)
+
+func main() {
+    // Create git interface for task management
+    gitInterface := git.DefaultGit("./tasks-repo")
+    
+    // Create task manager
+    taskManager := git_tm.NewGitTaskManager(gitInterface, "./tasks-repo")
+
+    // Create LLM configuration
+    llmConfig := llm.Config{
+        Provider: llm.ProviderOpenAI,
+        APIKey:   "your-openai-api-key-here",
+        BaseURL:  "https://api.openai.com/v1",
+        Timeout:  30 * time.Second,
+    }
+
+    // Create agent configuration
+    config := agent.AgentConfig{
+        Name:        "backend-engineer-1",
+        Role:        "Backend Engineer",
+        GitUsername: "backend-agent",
+        GitEmail:    "backend-agent@company.com",
+        WorkingDir:  "./workspace",
+        LLMProvider: llm.ProviderOpenAI,
+        LLMModel:    "gpt-4",
+        LLMConfig:   llmConfig,
+        SystemPrompt: `You are a skilled backend engineer. Your role is to:
+1. Analyze tasks and provide technical solutions
+2. Write clean, maintainable code
+3. Consider performance, security, and scalability
+4. Provide clear documentation for your solutions
+5. Follow best practices and coding standards`,
+        TaskManager: taskManager,
+        GitRepoPath: "./code-repo",
+        GitRemote:   "origin",
+        GitBranch:   "main",
+    }
+
+    // Create agent
+    agent, err := agent.NewAgent(config)
+    if err != nil {
+        log.Fatalf("Failed to create agent: %v", err)
+    }
+
+    // Run the agent
+    if err := agent.Run(); err != nil {
+        log.Fatalf("Agent failed: %v", err)
+    }
+}
+```
+
+### 2. Create a Task
+
+```go
+// Create a task for the agent to process
+ctx := context.Background()
+task, err := taskManager.CreateTask(ctx, &tm.TaskCreateRequest{
+    Title:       "Implement user authentication API",
+    Description: "Create a REST API endpoint for user authentication with JWT tokens. Include login, logout, and token refresh functionality.",
+    OwnerID:     "backend-engineer-1", // Must match agent name
+    Priority:    tm.PriorityHigh,
+})
+if err != nil {
+    log.Fatalf("Failed to create task: %v", err)
+}
+```
+
+## Configuration
+
+### AgentConfig
+
+The `AgentConfig` struct contains all configuration for an agent:
+
+```go
+type AgentConfig struct {
+    Name        string        // Agent identifier
+    Role        string        // Agent role (e.g., "Backend Engineer")
+    GitUsername string        // Git username for commits
+    GitEmail    string        // Git email for commits
+    WorkingDir  string        // Working directory for files
+    
+    // LLM Configuration
+    LLMProvider llm.Provider  // LLM provider type
+    LLMModel    string        // Model name (e.g., "gpt-4")
+    LLMConfig   llm.Config    // LLM provider configuration
+    
+    // System prompt for the agent
+    SystemPrompt string       // Instructions for the LLM
+    
+    // Task Manager Configuration
+    TaskManager tm.TaskManager // Task management interface
+    
+    // Git Configuration
+    GitRepoPath string        // Path to git repository
+    GitRemote   string        // Remote name (usually "origin")
+    GitBranch   string        // Default branch name
+}
+```
+
+### System Prompts
+
+System prompts define the agent's behavior and expertise. Here are some examples:
+
+#### Backend Engineer
+```
+You are a skilled backend engineer. Your role is to:
+1. Analyze tasks and provide technical solutions
+2. Write clean, maintainable code
+3. Consider performance, security, and scalability
+4. Provide clear documentation for your solutions
+5. Follow best practices and coding standards
+
+When responding to tasks, provide:
+- Detailed technical analysis
+- Code examples where appropriate
+- Implementation considerations
+- Testing recommendations
+- Documentation suggestions
+```
+
+#### Frontend Engineer
+```
+You are a frontend engineer. Focus on:
+- User interface design and implementation
+- React/Vue/Angular development
+- Responsive design and accessibility
+- Performance optimization
+- User experience best practices
+```
+
+#### Product Manager
+```
+You are a product manager. Focus on:
+- Product strategy and roadmap
+- User research and requirements gathering
+- Feature prioritization and planning
+- Stakeholder communication
+- Product documentation and specifications
+```
+
+## How It Works
+
+### 1. Task Processing Loop
+
+The agent runs in an infinite loop that:
+
+1. **Fetches Tasks**: Gets tasks assigned to the agent from the task manager
+2. **Filters Tasks**: Looks for tasks with "todo" status
+3. **Starts Task**: Marks the task as "in progress"
+4. **Processes with LLM**: Sends task description to LLM for solution
+5. **Creates PR**: Creates a git branch and pull request with the solution
+6. **Completes Task**: Marks the task as completed
+
+### 2. Git Operations
+
+For each task, the agent:
+
+1. Creates a new branch: `task/{task-id}-{clean-title}`
+2. Writes solution to a markdown file
+3. Commits the solution
+4. Pushes the branch to create a pull request
+
+### 3. Solution Format
+
+Solutions are formatted as markdown files containing:
+
+- Task metadata (ID, title, agent info)
+- Original task description
+- LLM-generated solution
+- Timestamp and attribution
+
+## Multiple Agents
+
+You can run multiple agents with different roles:
+
+```go
+// Create agents with different roles
+agents := []agent.AgentConfig{
+    {
+        Name:        "backend-engineer-1",
+        Role:        "Backend Engineer",
+        // ... backend configuration
+    },
+    {
+        Name:        "frontend-engineer-1",
+        Role:        "Frontend Engineer",
+        // ... frontend configuration
+    },
+    {
+        Name:        "product-manager-1",
+        Role:        "Product Manager",
+        // ... product manager configuration
+    },
+}
+
+// Start all agents
+for _, config := range agents {
+    agent, err := agent.NewAgent(config)
+    if err != nil {
+        log.Printf("Failed to create agent %s: %v", config.Name, err)
+        continue
+    }
+
+    go func(agent *agent.Agent, name string) {
+        log.Printf("Starting agent: %s", name)
+        if err := agent.Run(); err != nil {
+            log.Printf("Agent %s stopped with error: %v", name, err)
+        }
+    }(agent, config.Name)
+}
+```
+
+## Error Handling
+
+The agent includes robust error handling:
+
+- **Configuration Validation**: Validates all required fields
+- **Graceful Recovery**: Continues running even if individual tasks fail
+- **Logging**: Comprehensive logging of all operations
+- **Resource Cleanup**: Proper cleanup of LLM connections
+
+## Testing
+
+Run the tests with:
+
+```bash
+go test ./server/agent/...
+```
+
+The test suite includes:
+
+- Configuration validation
+- Branch name generation
+- Task prompt building
+- Solution formatting
+- Error handling
+
+## Dependencies
+
+The agent package depends on:
+
+- `github.com/iomodo/staff/llm` - LLM service interface
+- `github.com/iomodo/staff/tm` - Task management interface
+- `github.com/iomodo/staff/git` - Git operations interface
+
+## Examples
+
+See `example.go` for complete working examples:
+
+- `ExampleAgent()` - Single agent setup
+- `ExampleMultipleAgents()` - Multiple agents with different roles
+
+## Best Practices
+
+1. **Unique Agent Names**: Ensure each agent has a unique name
+2. **Role-Specific Prompts**: Tailor system prompts to the agent's role
+3. **Task Assignment**: Assign tasks to agents by setting the `OwnerID` to the agent's name
+4. **Monitoring**: Monitor agent logs for errors and performance
+5. **Resource Management**: Ensure proper cleanup when stopping agents 
\ No newline at end of file
diff --git a/server/agent/agent.go b/server/agent/agent.go
index e901cf6..e3aa766 100644
--- a/server/agent/agent.go
+++ b/server/agent/agent.go
@@ -1,23 +1,399 @@
 package agent
 
+import (
+	"context"
+	"fmt"
+	"log"
+	"os"
+	"path/filepath"
+	"strings"
+	"time"
+
+	"github.com/iomodo/staff/git"
+	"github.com/iomodo/staff/llm"
+	"github.com/iomodo/staff/tm"
+)
+
+// AgentConfig contains configuration for the agent
 type AgentConfig struct {
 	Name        string
 	Role        string
 	GitUsername string
 	GitEmail    string
 	WorkingDir  string
+
+	// LLM Configuration
+	LLMProvider llm.Provider
+	LLMModel    string
+	LLMConfig   llm.Config
+
+	// System prompt for the agent
+	SystemPrompt string
+
+	// Task Manager Configuration
+	TaskManager tm.TaskManager
+
+	// Git Configuration
+	GitRepoPath string
+	GitRemote   string
+	GitBranch   string
 }
 
+// Agent represents an AI agent that can process tasks
 type Agent struct {
-	Config AgentConfig
+	Config       AgentConfig
+	llmProvider  llm.LLMProvider
+	gitInterface git.GitInterface
+	ctx          context.Context
+	cancel       context.CancelFunc
 }
 
-func NewAgent(config AgentConfig) *Agent {
-	return &Agent{
-		Config: config,
+// NewAgent creates a new agent instance
+func NewAgent(config AgentConfig) (*Agent, error) {
+	// Validate configuration
+	if err := validateConfig(config); err != nil {
+		return nil, fmt.Errorf("invalid config: %w", err)
+	}
+
+	// Create LLM provider
+	llmProvider, err := llm.CreateProvider(config.LLMConfig)
+	if err != nil {
+		return nil, fmt.Errorf("failed to create LLM provider: %w", err)
+	}
+
+	// Create git interface
+	gitInterface := git.DefaultGit(config.GitRepoPath)
+
+	// Create context with cancellation
+	ctx, cancel := context.WithCancel(context.Background())
+
+	agent := &Agent{
+		Config:       config,
+		llmProvider:  llmProvider,
+		gitInterface: gitInterface,
+		ctx:          ctx,
+		cancel:       cancel,
+	}
+
+	return agent, nil
+}
+
+// validateConfig validates the agent configuration
+func validateConfig(config AgentConfig) error {
+	if config.Name == "" {
+		return fmt.Errorf("agent name is required")
+	}
+	if config.Role == "" {
+		return fmt.Errorf("agent role is required")
+	}
+	if config.WorkingDir == "" {
+		return fmt.Errorf("working directory is required")
+	}
+	if config.SystemPrompt == "" {
+		return fmt.Errorf("system prompt is required")
+	}
+	if config.TaskManager == nil {
+		return fmt.Errorf("task manager is required")
+	}
+	if config.GitRepoPath == "" {
+		return fmt.Errorf("git repository path is required")
+	}
+	return nil
+}
+
+// Run starts the agent's main loop
+func (a *Agent) Run() error {
+	log.Printf("Starting agent %s (%s)", a.Config.Name, a.Config.Role)
+	defer log.Printf("Agent %s stopped", a.Config.Name)
+
+	// Initialize git repository if needed
+	if err := a.initializeGit(); err != nil {
+		return fmt.Errorf("failed to initialize git: %w", err)
+	}
+
+	// Main agent loop
+	for {
+		select {
+		case <-a.ctx.Done():
+			return a.ctx.Err()
+		default:
+			if err := a.processNextTask(); err != nil {
+				log.Printf("Error processing task: %v", err)
+				// Continue running even if there's an error
+				time.Sleep(30 * time.Second)
+			}
+		}
 	}
 }
 
-func (a *Agent) Run() {
+// Stop stops the agent
+func (a *Agent) Stop() {
+	log.Printf("Stopping agent %s", a.Config.Name)
+	a.cancel()
+	if a.llmProvider != nil {
+		a.llmProvider.Close()
+	}
+}
 
+// initializeGit initializes the git repository
+func (a *Agent) initializeGit() error {
+	ctx := context.Background()
+
+	// Check if repository exists
+	isRepo, err := a.gitInterface.IsRepository(ctx, a.Config.GitRepoPath)
+	if err != nil {
+		return fmt.Errorf("failed to check repository: %w", err)
+	}
+
+	if !isRepo {
+		// Initialize new repository
+		if err := a.gitInterface.Init(ctx, a.Config.GitRepoPath); err != nil {
+			return fmt.Errorf("failed to initialize repository: %w", err)
+		}
+	}
+
+	// Set git user configuration
+	userConfig := git.UserConfig{
+		Name:  a.Config.GitUsername,
+		Email: a.Config.GitEmail,
+	}
+	if err := a.gitInterface.SetUserConfig(ctx, userConfig); err != nil {
+		return fmt.Errorf("failed to set git user config: %w", err)
+	}
+
+	// Checkout to the specified branch
+	if a.Config.GitBranch != "" {
+		if err := a.gitInterface.Checkout(ctx, a.Config.GitBranch); err != nil {
+			// Try to create the branch if it doesn't exist
+			if err := a.gitInterface.CreateBranch(ctx, a.Config.GitBranch, ""); err != nil {
+				return fmt.Errorf("failed to create branch %s: %w", a.Config.GitBranch, err)
+			}
+		}
+	}
+
+	return nil
+}
+
+// processNextTask processes the next available task
+func (a *Agent) processNextTask() error {
+	ctx := context.Background()
+
+	// Get tasks assigned to this agent
+	taskList, err := a.Config.TaskManager.GetTasksByOwner(ctx, a.Config.Name, 0, 10)
+	if err != nil {
+		return fmt.Errorf("failed to get tasks: %w", err)
+	}
+
+	// Find a task that's ready to be worked on
+	var taskToProcess *tm.Task
+	for _, task := range taskList.Tasks {
+		if task.Status == tm.StatusToDo {
+			taskToProcess = task
+			break
+		}
+	}
+
+	if taskToProcess == nil {
+		// No tasks to process, wait a bit
+		time.Sleep(60 * time.Second)
+		return nil
+	}
+
+	log.Printf("Processing task: %s - %s", taskToProcess.ID, taskToProcess.Title)
+
+	// Start the task
+	startedTask, err := a.Config.TaskManager.StartTask(ctx, taskToProcess.ID)
+	if err != nil {
+		return fmt.Errorf("failed to start task: %w", err)
+	}
+
+	// Process the task with LLM
+	solution, err := a.processTaskWithLLM(startedTask)
+	if err != nil {
+		// Mark task as failed or retry
+		log.Printf("Failed to process task with LLM: %v", err)
+		return err
+	}
+
+	// Create PR with the solution
+	if err := a.createPullRequest(startedTask, solution); err != nil {
+		return fmt.Errorf("failed to create pull request: %w", err)
+	}
+
+	// Complete the task
+	if _, err := a.Config.TaskManager.CompleteTask(ctx, startedTask.ID); err != nil {
+		return fmt.Errorf("failed to complete task: %w", err)
+	}
+
+	log.Printf("Successfully completed task: %s", startedTask.ID)
+	return nil
+}
+
+// processTaskWithLLM sends the task to the LLM and gets a solution
+func (a *Agent) processTaskWithLLM(task *tm.Task) (string, error) {
+	ctx := context.Background()
+
+	// Prepare the prompt
+	prompt := a.buildTaskPrompt(task)
+
+	// Create chat completion request
+	req := llm.ChatCompletionRequest{
+		Model: a.Config.LLMModel,
+		Messages: []llm.Message{
+			{
+				Role:    llm.RoleSystem,
+				Content: a.Config.SystemPrompt,
+			},
+			{
+				Role:    llm.RoleUser,
+				Content: prompt,
+			},
+		},
+		MaxTokens:   intPtr(4000),
+		Temperature: float64Ptr(0.7),
+	}
+
+	// Get response from LLM
+	resp, err := a.llmProvider.ChatCompletion(ctx, req)
+	if err != nil {
+		return "", fmt.Errorf("LLM chat completion failed: %w", err)
+	}
+
+	if len(resp.Choices) == 0 {
+		return "", fmt.Errorf("no response from LLM")
+	}
+
+	return resp.Choices[0].Message.Content, nil
+}
+
+// buildTaskPrompt builds the prompt for the LLM based on the task
+func (a *Agent) buildTaskPrompt(task *tm.Task) string {
+	var prompt strings.Builder
+
+	prompt.WriteString(fmt.Sprintf("Task ID: %s\n", task.ID))
+	prompt.WriteString(fmt.Sprintf("Title: %s\n", task.Title))
+	prompt.WriteString(fmt.Sprintf("Priority: %s\n", task.Priority))
+
+	if task.Description != "" {
+		prompt.WriteString(fmt.Sprintf("Description: %s\n", task.Description))
+	}
+
+	if task.DueDate != nil {
+		prompt.WriteString(fmt.Sprintf("Due Date: %s\n", task.DueDate.Format("2006-01-02")))
+	}
+
+	prompt.WriteString("\nPlease provide a detailed solution for this task. ")
+	prompt.WriteString("Include any code, documentation, or other deliverables as needed. ")
+	prompt.WriteString("Format your response appropriately for the type of task.")
+
+	return prompt.String()
+}
+
+// createPullRequest creates a pull request with the solution
+func (a *Agent) createPullRequest(task *tm.Task, solution string) error {
+	ctx := context.Background()
+
+	// Generate branch name
+	branchName := a.generateBranchName(task)
+
+	// Create and checkout to new branch
+	if err := a.gitInterface.CreateBranch(ctx, branchName, ""); err != nil {
+		return fmt.Errorf("failed to create branch: %w", err)
+	}
+
+	if err := a.gitInterface.Checkout(ctx, branchName); err != nil {
+		return fmt.Errorf("failed to checkout branch: %w", err)
+	}
+
+	// Create solution file
+	solutionPath := filepath.Join(a.Config.WorkingDir, fmt.Sprintf("task-%s-solution.md", task.ID))
+	solutionContent := a.formatSolution(task, solution)
+
+	if err := os.WriteFile(solutionPath, []byte(solutionContent), 0644); err != nil {
+		return fmt.Errorf("failed to write solution file: %w", err)
+	}
+
+	// Add and commit the solution
+	if err := a.gitInterface.Add(ctx, []string{solutionPath}); err != nil {
+		return fmt.Errorf("failed to add solution file: %w", err)
+	}
+
+	commitMessage := fmt.Sprintf("feat: Complete task %s - %s", task.ID, task.Title)
+	if err := a.gitInterface.Commit(ctx, commitMessage, git.CommitOptions{}); err != nil {
+		return fmt.Errorf("failed to commit solution: %w", err)
+	}
+
+	// Push the branch
+	if err := a.gitInterface.Push(ctx, "origin", branchName, git.PushOptions{SetUpstream: true}); err != nil {
+		return fmt.Errorf("failed to push branch: %w", err)
+	}
+
+	log.Printf("Created pull request for task %s on branch %s", task.ID, branchName)
+	return nil
+}
+
+// generateBranchName generates a branch name for the task
+func (a *Agent) generateBranchName(task *tm.Task) string {
+	// Clean the task title for branch name
+	cleanTitle := strings.ReplaceAll(task.Title, " ", "-")
+	cleanTitle = strings.ToLower(cleanTitle)
+
+	// Remove special characters that are not allowed in git branch names
+	// Keep only alphanumeric characters and hyphens
+	var result strings.Builder
+	for _, char := range cleanTitle {
+		if (char >= 'a' && char <= 'z') || (char >= '0' && char <= '9') || char == '-' {
+			result.WriteRune(char)
+		}
+	}
+	cleanTitle = result.String()
+
+	// Remove consecutive hyphens
+	for strings.Contains(cleanTitle, "--") {
+		cleanTitle = strings.ReplaceAll(cleanTitle, "--", "-")
+	}
+
+	// Remove leading and trailing hyphens
+	cleanTitle = strings.Trim(cleanTitle, "-")
+
+	// Limit length
+	if len(cleanTitle) > 50 {
+		cleanTitle = cleanTitle[:50]
+		// Ensure we don't end with a hyphen after truncation
+		cleanTitle = strings.TrimSuffix(cleanTitle, "-")
+	}
+
+	return fmt.Sprintf("task/%s-%s", task.ID, cleanTitle)
+}
+
+// formatSolution formats the solution for the pull request
+func (a *Agent) formatSolution(task *tm.Task, solution string) string {
+	var content strings.Builder
+
+	content.WriteString(fmt.Sprintf("# Task Solution: %s\n\n", task.Title))
+	content.WriteString(fmt.Sprintf("**Task ID:** %s\n", task.ID))
+	content.WriteString(fmt.Sprintf("**Agent:** %s (%s)\n", a.Config.Name, a.Config.Role))
+	content.WriteString(fmt.Sprintf("**Completed:** %s\n\n", time.Now().Format("2006-01-02 15:04:05")))
+
+	content.WriteString("## Task Description\n\n")
+	content.WriteString(task.Description)
+	content.WriteString("\n\n")
+
+	content.WriteString("## Solution\n\n")
+	content.WriteString(solution)
+	content.WriteString("\n\n")
+
+	content.WriteString("---\n")
+	content.WriteString("*This solution was generated by AI Agent*\n")
+
+	return content.String()
+}
+
+// ptr helpers for cleaner code
+func intPtr(i int) *int {
+	return &i
+}
+
+func float64Ptr(f float64) *float64 {
+	return &f
 }
diff --git a/server/agent/agent_test.go b/server/agent/agent_test.go
new file mode 100644
index 0000000..405e5b7
--- /dev/null
+++ b/server/agent/agent_test.go
@@ -0,0 +1,348 @@
+package agent
+
+import (
+	"context"
+	"os"
+	"path/filepath"
+	"testing"
+	"time"
+
+	"github.com/iomodo/staff/git"
+	"github.com/iomodo/staff/llm"
+	"github.com/iomodo/staff/tm"
+	"github.com/iomodo/staff/tm/git_tm"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+// MockLLMProvider implements LLMProvider for testing
+type MockLLMProvider struct{}
+
+func (m *MockLLMProvider) ChatCompletion(ctx context.Context, req llm.ChatCompletionRequest) (*llm.ChatCompletionResponse, error) {
+	return &llm.ChatCompletionResponse{
+		ID:    "mock-response-id",
+		Model: req.Model,
+		Choices: []llm.ChatCompletionChoice{
+			{
+				Index: 0,
+				Message: llm.Message{
+					Role:    llm.RoleAssistant,
+					Content: "This is a mock response for testing purposes.",
+				},
+				FinishReason: "stop",
+			},
+		},
+		Usage: llm.Usage{
+			PromptTokens:     10,
+			CompletionTokens: 20,
+			TotalTokens:      30,
+		},
+		Provider: llm.ProviderOpenAI,
+	}, nil
+}
+
+func (m *MockLLMProvider) CreateEmbeddings(ctx context.Context, req llm.EmbeddingRequest) (*llm.EmbeddingResponse, error) {
+	return &llm.EmbeddingResponse{
+		Object: "list",
+		Data: []llm.Embedding{
+			{
+				Object:    "embedding",
+				Embedding: []float64{0.1, 0.2, 0.3},
+				Index:     0,
+			},
+		},
+		Usage: llm.Usage{
+			PromptTokens: 5,
+			TotalTokens:  5,
+		},
+		Model:    req.Model,
+		Provider: llm.ProviderOpenAI,
+	}, nil
+}
+
+func (m *MockLLMProvider) Close() error {
+	return nil
+}
+
+// MockLLMFactory implements ProviderFactory for testing
+type MockLLMFactory struct{}
+
+func (f *MockLLMFactory) CreateProvider(config llm.Config) (llm.LLMProvider, error) {
+	return &MockLLMProvider{}, nil
+}
+
+func (f *MockLLMFactory) SupportsProvider(provider llm.Provider) bool {
+	return provider == llm.ProviderOpenAI
+}
+
+func setupTestAgent(t *testing.T) (*Agent, func()) {
+	// Create temporary directories
+	tempDir, err := os.MkdirTemp("", "agent-test")
+	require.NoError(t, err)
+
+	tasksDir := filepath.Join(tempDir, "tasks")
+	workspaceDir := filepath.Join(tempDir, "workspace")
+	codeRepoDir := filepath.Join(tempDir, "code-repo")
+
+	// Create directories
+	require.NoError(t, os.MkdirAll(tasksDir, 0755))
+	require.NoError(t, os.MkdirAll(workspaceDir, 0755))
+	require.NoError(t, os.MkdirAll(codeRepoDir, 0755))
+
+	// Initialize git repositories
+	gitInterface := git.DefaultGit(tasksDir)
+	ctx := context.Background()
+
+	err = gitInterface.Init(ctx, tasksDir)
+	require.NoError(t, err)
+
+	// Set git user config
+	userConfig := git.UserConfig{
+		Name:  "Test User",
+		Email: "test@example.com",
+	}
+	err = gitInterface.SetUserConfig(ctx, userConfig)
+	require.NoError(t, err)
+
+	// Create task manager
+	taskManager := git_tm.NewGitTaskManager(gitInterface, tasksDir)
+
+	// Create LLM config (using a mock configuration)
+	llmConfig := llm.Config{
+		Provider: llm.ProviderOpenAI,
+		APIKey:   "test-key",
+		BaseURL:  "https://api.openai.com/v1",
+		Timeout:  30 * time.Second,
+	}
+
+	// Create agent config
+	config := AgentConfig{
+		Name:         "test-agent",
+		Role:         "Test Engineer",
+		GitUsername:  "test-agent",
+		GitEmail:     "test-agent@test.com",
+		WorkingDir:   workspaceDir,
+		LLMProvider:  llm.ProviderOpenAI,
+		LLMModel:     "gpt-3.5-turbo",
+		LLMConfig:    llmConfig,
+		SystemPrompt: "You are a test agent. Provide simple, clear solutions.",
+		TaskManager:  taskManager,
+		GitRepoPath:  codeRepoDir,
+		GitRemote:    "origin",
+		GitBranch:    "main",
+	}
+
+	// Create agent with mock LLM provider
+	agent := &Agent{
+		Config:       config,
+		llmProvider:  &MockLLMProvider{},
+		gitInterface: git.DefaultGit(codeRepoDir),
+		ctx:          context.Background(),
+		cancel:       func() {},
+	}
+
+	cleanup := func() {
+		agent.Stop()
+		os.RemoveAll(tempDir)
+	}
+
+	return agent, cleanup
+}
+
+func TestNewAgent(t *testing.T) {
+	agent, cleanup := setupTestAgent(t)
+	defer cleanup()
+
+	assert.NotNil(t, agent)
+	assert.Equal(t, "test-agent", agent.Config.Name)
+	assert.Equal(t, "Test Engineer", agent.Config.Role)
+}
+
+func TestValidateConfig(t *testing.T) {
+	// Test valid config
+	validConfig := AgentConfig{
+		Name:         "test",
+		Role:         "test",
+		WorkingDir:   "/tmp",
+		SystemPrompt: "test",
+		TaskManager:  &git_tm.GitTaskManager{},
+		GitRepoPath:  "/tmp",
+	}
+
+	err := validateConfig(validConfig)
+	assert.NoError(t, err)
+
+	// Test invalid configs
+	testCases := []struct {
+		name   string
+		config AgentConfig
+	}{
+		{"empty name", AgentConfig{Role: "test", WorkingDir: "/tmp", SystemPrompt: "test", TaskManager: &git_tm.GitTaskManager{}, GitRepoPath: "/tmp"}},
+		{"empty role", AgentConfig{Name: "test", WorkingDir: "/tmp", SystemPrompt: "test", TaskManager: &git_tm.GitTaskManager{}, GitRepoPath: "/tmp"}},
+		{"empty working dir", AgentConfig{Name: "test", Role: "test", SystemPrompt: "test", TaskManager: &git_tm.GitTaskManager{}, GitRepoPath: "/tmp"}},
+		{"empty system prompt", AgentConfig{Name: "test", Role: "test", WorkingDir: "/tmp", SystemPrompt: "test", GitRepoPath: "/tmp"}},
+		{"nil task manager", AgentConfig{Name: "test", Role: "test", WorkingDir: "/tmp", SystemPrompt: "test", GitRepoPath: "/tmp"}},
+		{"empty git repo path", AgentConfig{Name: "test", Role: "test", WorkingDir: "/tmp", SystemPrompt: "test", TaskManager: &git_tm.GitTaskManager{}}},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.name, func(t *testing.T) {
+			err := validateConfig(tc.config)
+			assert.Error(t, err)
+		})
+	}
+}
+
+func TestGenerateBranchName(t *testing.T) {
+	agent, cleanup := setupTestAgent(t)
+	defer cleanup()
+
+	task := &tm.Task{
+		ID:    "task-123",
+		Title: "Implement User Authentication",
+	}
+
+	branchName := agent.generateBranchName(task)
+	assert.Contains(t, branchName, "task-123")
+	assert.Contains(t, branchName, "implement-user-authentication")
+}
+
+func TestBuildTaskPrompt(t *testing.T) {
+	agent, cleanup := setupTestAgent(t)
+	defer cleanup()
+
+	dueDate := time.Now().AddDate(0, 0, 7)
+	task := &tm.Task{
+		ID:          "task-123",
+		Title:       "Test Task",
+		Description: "This is a test task",
+		Priority:    tm.PriorityHigh,
+		DueDate:     &dueDate,
+	}
+
+	prompt := agent.buildTaskPrompt(task)
+	assert.Contains(t, prompt, "task-123")
+	assert.Contains(t, prompt, "Test Task")
+	assert.Contains(t, prompt, "This is a test task")
+	assert.Contains(t, prompt, "high")
+}
+
+func TestFormatSolution(t *testing.T) {
+	agent, cleanup := setupTestAgent(t)
+	defer cleanup()
+
+	task := &tm.Task{
+		ID:          "task-123",
+		Title:       "Test Task",
+		Description: "This is a test task description",
+		Priority:    tm.PriorityMedium,
+	}
+
+	solution := "This is the solution to the task."
+	formatted := agent.formatSolution(task, solution)
+
+	assert.Contains(t, formatted, "# Task Solution: Test Task")
+	assert.Contains(t, formatted, "**Task ID:** task-123")
+	assert.Contains(t, formatted, "**Agent:** test-agent (Test Engineer)")
+	assert.Contains(t, formatted, "## Task Description")
+	assert.Contains(t, formatted, "This is a test task description")
+	assert.Contains(t, formatted, "## Solution")
+	assert.Contains(t, formatted, "This is the solution to the task.")
+	assert.Contains(t, formatted, "*This solution was generated by AI Agent*")
+}
+
+func TestAgentStop(t *testing.T) {
+	agent, cleanup := setupTestAgent(t)
+	defer cleanup()
+
+	// Test that Stop doesn't panic
+	assert.NotPanics(t, func() {
+		agent.Stop()
+	})
+}
+
+func TestGenerateBranchNameWithSpecialCharacters(t *testing.T) {
+	agent, cleanup := setupTestAgent(t)
+	defer cleanup()
+
+	testCases := []struct {
+		title    string
+		expected string
+	}{
+		{
+			title:    "Simple Task",
+			expected: "task/task-123-simple-task",
+		},
+		{
+			title:    "Task with (parentheses) and [brackets]",
+			expected: "task/task-123-task-with-parentheses-and-brackets",
+		},
+		{
+			title:    "Very Long Task Title That Should Be Truncated Because It Exceeds The Maximum Length Allowed For Branch Names",
+			expected: "task/task-123-very-long-task-title-that-should-be-truncated-beca",
+		},
+	}
+
+	for _, tc := range testCases {
+		t.Run(tc.title, func(t *testing.T) {
+			task := &tm.Task{
+				ID:    "task-123",
+				Title: tc.title,
+			}
+
+			branchName := agent.generateBranchName(task)
+			assert.Equal(t, tc.expected, branchName)
+		})
+	}
+}
+
+func TestProcessTaskWithLLM(t *testing.T) {
+	agent, cleanup := setupTestAgent(t)
+	defer cleanup()
+
+	task := &tm.Task{
+		ID:          "task-123",
+		Title:       "Test Task",
+		Description: "This is a test task",
+		Priority:    tm.PriorityHigh,
+	}
+
+	solution, err := agent.processTaskWithLLM(task)
+	assert.NoError(t, err)
+	assert.Contains(t, solution, "mock response")
+}
+
+func TestMockLLMProvider(t *testing.T) {
+	mockProvider := &MockLLMProvider{}
+
+	// Test ChatCompletion
+	req := llm.ChatCompletionRequest{
+		Model: "gpt-3.5-turbo",
+		Messages: []llm.Message{
+			{Role: llm.RoleUser, Content: "Hello"},
+		},
+	}
+
+	resp, err := mockProvider.ChatCompletion(context.Background(), req)
+	assert.NoError(t, err)
+	assert.NotNil(t, resp)
+	assert.Equal(t, "gpt-3.5-turbo", resp.Model)
+	assert.Len(t, resp.Choices, 1)
+	assert.Contains(t, resp.Choices[0].Message.Content, "mock response")
+
+	// Test CreateEmbeddings
+	embedReq := llm.EmbeddingRequest{
+		Input: "test",
+		Model: "text-embedding-ada-002",
+	}
+
+	embedResp, err := mockProvider.CreateEmbeddings(context.Background(), embedReq)
+	assert.NoError(t, err)
+	assert.NotNil(t, embedResp)
+	assert.Len(t, embedResp.Data, 1)
+	assert.Len(t, embedResp.Data[0].Embedding, 3)
+
+	// Test Close
+	err = mockProvider.Close()
+	assert.NoError(t, err)
+}
diff --git a/server/agent/example.go b/server/agent/example.go
new file mode 100644
index 0000000..1233bdc
--- /dev/null
+++ b/server/agent/example.go
@@ -0,0 +1,196 @@
+package agent
+
+import (
+	"context"
+	"log"
+	"time"
+
+	"github.com/iomodo/staff/git"
+	"github.com/iomodo/staff/llm"
+	"github.com/iomodo/staff/tm"
+	"github.com/iomodo/staff/tm/git_tm"
+)
+
+// ExampleAgent demonstrates how to create and run an agent
+func ExampleAgent() {
+	// Create git interface for task management
+	gitInterface := git.DefaultGit("./tasks-repo")
+
+	// Create task manager
+	taskManager := git_tm.NewGitTaskManager(gitInterface, "./tasks-repo")
+
+	// Create LLM configuration
+	llmConfig := llm.Config{
+		Provider: llm.ProviderOpenAI,
+		APIKey:   "your-openai-api-key-here", // Replace with actual API key
+		BaseURL:  "https://api.openai.com/v1",
+		Timeout:  30 * time.Second,
+	}
+
+	// Create agent configuration
+	config := AgentConfig{
+		Name:        "backend-engineer-1",
+		Role:        "Backend Engineer",
+		GitUsername: "backend-agent",
+		GitEmail:    "backend-agent@company.com",
+		WorkingDir:  "./workspace",
+		LLMProvider: llm.ProviderOpenAI,
+		LLMModel:    "gpt-4",
+		LLMConfig:   llmConfig,
+		SystemPrompt: `You are a skilled backend engineer. Your role is to:
+1. Analyze tasks and provide technical solutions
+2. Write clean, maintainable code
+3. Consider performance, security, and scalability
+4. Provide clear documentation for your solutions
+5. Follow best practices and coding standards
+
+When responding to tasks, provide:
+- Detailed technical analysis
+- Code examples where appropriate
+- Implementation considerations
+- Testing recommendations
+- Documentation suggestions`,
+		TaskManager: taskManager,
+		GitRepoPath: "./code-repo",
+		GitRemote:   "origin",
+		GitBranch:   "main",
+	}
+
+	// Create agent
+	agent, err := NewAgent(config)
+	if err != nil {
+		log.Fatalf("Failed to create agent: %v", err)
+	}
+
+	// Create a sample task
+	ctx := context.Background()
+	task, err := taskManager.CreateTask(ctx, &tm.TaskCreateRequest{
+		Title:       "Implement user authentication API",
+		Description: "Create a REST API endpoint for user authentication with JWT tokens. Include login, logout, and token refresh functionality.",
+		OwnerID:     config.Name,
+		Priority:    tm.PriorityHigh,
+	})
+	if err != nil {
+		log.Fatalf("Failed to create task: %v", err)
+	}
+
+	log.Printf("Created task: %s", task.ID)
+
+	// Run the agent (this will process tasks in an infinite loop)
+	go func() {
+		if err := agent.Run(); err != nil {
+			log.Printf("Agent stopped with error: %v", err)
+		}
+	}()
+
+	// Let the agent run for a while
+	time.Sleep(5 * time.Minute)
+
+	// Stop the agent
+	agent.Stop()
+}
+
+// ExampleMultipleAgents demonstrates how to create multiple agents with different roles
+func ExampleMultipleAgents() {
+	// Create shared git interface for task management
+	gitInterface := git.DefaultGit("./tasks-repo")
+	taskManager := git_tm.NewGitTaskManager(gitInterface, "./tasks-repo")
+
+	// Create agents with different roles
+	agents := []AgentConfig{
+		{
+			Name:        "backend-engineer-1",
+			Role:        "Backend Engineer",
+			GitUsername: "backend-agent",
+			GitEmail:    "backend-agent@company.com",
+			WorkingDir:  "./workspace/backend",
+			LLMProvider: llm.ProviderOpenAI,
+			LLMModel:    "gpt-4",
+			LLMConfig: llm.Config{
+				Provider: llm.ProviderOpenAI,
+				APIKey:   "your-openai-api-key",
+				BaseURL:  "https://api.openai.com/v1",
+				Timeout:  30 * time.Second,
+			},
+			SystemPrompt: `You are a backend engineer. Focus on:
+- API design and implementation
+- Database design and optimization
+- Security best practices
+- Performance optimization
+- Code quality and testing`,
+			TaskManager: taskManager,
+			GitRepoPath: "./code-repo",
+			GitRemote:   "origin",
+			GitBranch:   "main",
+		},
+		{
+			Name:        "frontend-engineer-1",
+			Role:        "Frontend Engineer",
+			GitUsername: "frontend-agent",
+			GitEmail:    "frontend-agent@company.com",
+			WorkingDir:  "./workspace/frontend",
+			LLMProvider: llm.ProviderOpenAI,
+			LLMModel:    "gpt-4",
+			LLMConfig: llm.Config{
+				Provider: llm.ProviderOpenAI,
+				APIKey:   "your-openai-api-key",
+				BaseURL:  "https://api.openai.com/v1",
+				Timeout:  30 * time.Second,
+			},
+			SystemPrompt: `You are a frontend engineer. Focus on:
+- User interface design and implementation
+- React/Vue/Angular development
+- Responsive design and accessibility
+- Performance optimization
+- User experience best practices`,
+			TaskManager: taskManager,
+			GitRepoPath: "./code-repo",
+			GitRemote:   "origin",
+			GitBranch:   "main",
+		},
+		{
+			Name:        "product-manager-1",
+			Role:        "Product Manager",
+			GitUsername: "pm-agent",
+			GitEmail:    "pm-agent@company.com",
+			WorkingDir:  "./workspace/product",
+			LLMProvider: llm.ProviderOpenAI,
+			LLMModel:    "gpt-4",
+			LLMConfig: llm.Config{
+				Provider: llm.ProviderOpenAI,
+				APIKey:   "your-openai-api-key",
+				BaseURL:  "https://api.openai.com/v1",
+				Timeout:  30 * time.Second,
+			},
+			SystemPrompt: `You are a product manager. Focus on:
+- Product strategy and roadmap
+- User research and requirements gathering
+- Feature prioritization and planning
+- Stakeholder communication
+- Product documentation and specifications`,
+			TaskManager: taskManager,
+			GitRepoPath: "./docs-repo",
+			GitRemote:   "origin",
+			GitBranch:   "main",
+		},
+	}
+
+	// Create and start all agents
+	for _, config := range agents {
+		agent, err := NewAgent(config)
+		if err != nil {
+			log.Printf("Failed to create agent %s: %v", config.Name, err)
+			continue
+		}
+
+		go func(agent *Agent, name string) {
+			log.Printf("Starting agent: %s", name)
+			if err := agent.Run(); err != nil {
+				log.Printf("Agent %s stopped with error: %v", name, err)
+			}
+		}(agent, config.Name)
+	}
+
+	// Let agents run for a while
+	time.Sleep(10 * time.Minute)
+}