Add Agent manager

Change-Id: Iaa68e9228165bd274f9c5be9d4320ef49a009ca8
diff --git a/server/agent/agent.go b/server/agent/agent.go
deleted file mode 100644
index 1b27831..0000000
--- a/server/agent/agent.go
+++ /dev/null
@@ -1,580 +0,0 @@
-package agent
-
-import (
-	"context"
-	"fmt"
-	"log/slog"
-	"os"
-	"path/filepath"
-	"strings"
-	"time"
-
-	"github.com/iomodo/staff/git"
-	"github.com/iomodo/staff/llm"
-	_ "github.com/iomodo/staff/llm/openai" // Import for side effects (registers provider)
-	"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
-
-	// Gerrit Configuration
-	GerritEnabled bool
-	GerritConfig  GerritConfig
-}
-
-// GerritConfig holds configuration for Gerrit operations
-type GerritConfig struct {
-	Username string
-	Password string // Can be HTTP password or API token
-	BaseURL  string
-	Project  string
-}
-
-// Agent represents an AI agent that can process tasks
-type Agent struct {
-	Config       AgentConfig
-	llmProvider  llm.LLMProvider
-	gitInterface git.GitInterface
-	ctx          context.Context
-	cancel       context.CancelFunc
-	logger       *slog.Logger
-}
-
-// NewAgent creates a new agent instance
-func NewAgent(config AgentConfig, logger *slog.Logger) (*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
-	var gitInterface git.GitInterface
-	if config.GerritEnabled {
-		// Create Gerrit pull request provider
-		gerritPRProvider := git.NewGerritPullRequestProvider(config.GerritConfig.Project, git.GerritConfig{
-			Username:   config.GerritConfig.Username,
-			Password:   config.GerritConfig.Password,
-			BaseURL:    config.GerritConfig.BaseURL,
-			HTTPClient: nil, // Will use default client
-		})
-
-		// Create git interface with Gerrit pull request provider
-		gitConfig := git.GitConfig{
-			Timeout:             30 * time.Second,
-			PullRequestProvider: gerritPRProvider,
-		}
-		gitInterface = git.NewGitWithPullRequests(config.GitRepoPath, gitConfig, gerritPRProvider, logger)
-	} else {
-		// Use default git interface (GitHub)
-		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,
-		logger:       logger,
-	}
-
-	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
-}
-
-const (
-	// Agent polling intervals
-	TaskPollingInterval       = 60 * time.Second
-	ErrorRetryInterval        = 30 * time.Second
-	DefaultGitTimeout         = 30 * time.Second
-	DefaultContextTimeout     = 5 * time.Minute
-	DefaultMaxTaskRetries     = 3
-	DefaultLLMMaxTokens       = 4000
-	DefaultLLMTemperature     = 0.7
-)
-
-// Run starts the agent's main loop
-func (a *Agent) Run() error {
-	a.logger.Info("Starting agent", slog.String("name", a.Config.Name), slog.String("role", a.Config.Role))
-	defer a.logger.Info("Agent stopped", slog.String("name", 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 {
-				a.logger.Error("Error processing task", slog.String("error", err.Error()))
-				time.Sleep(ErrorRetryInterval)
-			}
-		}
-	}
-}
-
-// Stop stops the agent
-func (a *Agent) Stop() {
-	a.logger.Info("Stopping agent", slog.String("name", 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()
-
-	if err := a.ensureRepository(ctx); err != nil {
-		return err
-	}
-
-	if err := a.ensureRemoteOrigin(ctx); err != nil {
-		return err
-	}
-
-	if err := a.ensureTargetBranch(ctx); err != nil {
-		return err
-	}
-
-	return nil
-}
-
-// ensureRepository ensures the git repository is initialized
-func (a *Agent) ensureRepository(ctx context.Context) error {
-	isRepo, err := a.gitInterface.IsRepository(ctx, a.Config.GitRepoPath)
-	if err != nil {
-		return fmt.Errorf("failed to check repository: %w", err)
-	}
-
-	if !isRepo {
-		if err := a.gitInterface.Init(ctx, a.Config.GitRepoPath); err != nil {
-			return fmt.Errorf("failed to initialize repository: %w", err)
-		}
-	}
-
-	return nil
-}
-
-// ensureRemoteOrigin ensures the remote origin is configured
-func (a *Agent) ensureRemoteOrigin(ctx context.Context) error {
-	remotes, err := a.gitInterface.ListRemotes(ctx)
-	if err != nil {
-		return fmt.Errorf("failed to list remotes: %w", err)
-	}
-
-	// Check if origin already exists
-	for _, remote := range remotes {
-		if remote.Name == "origin" {
-			return nil
-		}
-	}
-
-	// Add remote origin
-	remoteURL := a.buildRemoteURL()
-	if err := a.gitInterface.AddRemote(ctx, "origin", remoteURL); err != nil {
-		return fmt.Errorf("failed to add remote origin: %w", err)
-	}
-
-	return nil
-}
-
-// buildRemoteURL builds the appropriate remote URL based on configuration
-func (a *Agent) buildRemoteURL() string {
-	if !a.Config.GerritEnabled {
-		return a.Config.GitRemote
-	}
-
-	// Build Gerrit URL
-	if strings.HasPrefix(a.Config.GerritConfig.BaseURL, "https://") {
-		return fmt.Sprintf("%s/%s.git", a.Config.GerritConfig.BaseURL, a.Config.GerritConfig.Project)
-	}
-
-	// SSH format
-	return fmt.Sprintf("ssh://%s@%s:29418/%s.git",
-		a.Config.GerritConfig.Username,
-		strings.TrimPrefix(a.Config.GerritConfig.BaseURL, "https://"),
-		a.Config.GerritConfig.Project)
-}
-
-// ensureTargetBranch ensures the agent is on the target branch
-func (a *Agent) ensureTargetBranch(ctx context.Context) error {
-	if a.Config.GitBranch == "" {
-		return nil
-	}
-
-	currentBranch, err := a.gitInterface.GetCurrentBranch(ctx)
-	if err != nil {
-		return fmt.Errorf("failed to get current branch: %w", err)
-	}
-
-	if currentBranch == a.Config.GitBranch {
-		a.logger.Info("Already on target branch", slog.String("branch", a.Config.GitBranch))
-		return nil
-	}
-
-	return a.checkoutOrCreateBranch(ctx, a.Config.GitBranch)
-}
-
-// checkoutOrCreateBranch attempts to checkout a branch, creating it if it doesn't exist
-func (a *Agent) checkoutOrCreateBranch(ctx context.Context, branchName string) error {
-	if err := a.gitInterface.Checkout(ctx, branchName); err != nil {
-		if a.isBranchNotFoundError(err) {
-			if createErr := a.gitInterface.CreateBranch(ctx, branchName, ""); createErr != nil {
-				return fmt.Errorf("failed to create branch %s: %w", branchName, createErr)
-			}
-			return nil
-		}
-		return fmt.Errorf("failed to checkout branch %s: %w", branchName, err)
-	}
-	return nil
-}
-
-// isBranchNotFoundError checks if the error indicates a branch doesn't exist
-func (a *Agent) isBranchNotFoundError(err error) bool {
-	errMsg := err.Error()
-	return strings.Contains(errMsg, "did not match any file(s) known to git") ||
-		strings.Contains(errMsg, "not found") ||
-		strings.Contains(errMsg, "unknown revision") ||
-		strings.Contains(errMsg, "reference is not a tree") ||
-		strings.Contains(errMsg, "pathspec") ||
-		strings.Contains(errMsg, "fatal: invalid reference")
-}
-
-// 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)
-	a.logger.Info("Total number of Tasks", slog.String("agent", a.Config.Name), slog.Any("tasks", taskList.TotalCount))
-	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
-		}
-	}
-
-	a.logger.Info("Task to process", slog.Any("task", taskToProcess))
-
-	if taskToProcess == nil {
-		// No tasks to process, wait a bit
-		time.Sleep(TaskPollingInterval)
-		return nil
-	}
-
-	a.logger.Info("Processing task", slog.String("id", taskToProcess.ID), slog.String("title", 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
-		a.logger.Error("Failed to process task with LLM", slog.String("error", err.Error()))
-		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)
-	}
-
-	a.logger.Info("Successfully completed task", slog.String("id", 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(DefaultLLMMaxTokens),
-		Temperature: float64Ptr(DefaultLLMTemperature),
-	}
-
-	// 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\n\n%s", task.ID, task.Title, a.formatPullRequestDescription(task, solution))
-	if err := a.gitInterface.Commit(ctx, commitMessage, git.CommitOptions{
-		Author: &git.Author{
-			Name:  a.Config.GitUsername,
-			Email: a.Config.GitEmail,
-			Time:  time.Now(),
-		},
-	}); err != nil {
-		return fmt.Errorf("failed to commit solution: %w", err)
-	}
-
-	if a.Config.GerritEnabled {
-		// For Gerrit: Push to refs/for/BRANCH to create a change
-		gerritRef := fmt.Sprintf("refs/for/%s", a.Config.GitBranch)
-		if err := a.gitInterface.Push(ctx, "origin", gerritRef, git.PushOptions{}); err != nil {
-			return fmt.Errorf("failed to push to Gerrit: %w", err)
-		}
-		a.logger.Info("Created Gerrit change for task", slog.String("id", task.ID), slog.String("ref", gerritRef))
-	} else {
-		// For GitHub: Push branch and create PR
-		if err := a.gitInterface.Push(ctx, "origin", branchName, git.PushOptions{SetUpstream: true}); err != nil {
-			return fmt.Errorf("failed to push branch: %w", err)
-		}
-
-		// Create pull request using the git interface
-		prOptions := git.PullRequestOptions{
-			Title:       fmt.Sprintf("Complete task %s: %s", task.ID, task.Title),
-			Description: a.formatPullRequestDescription(task, solution),
-			BaseBranch:  a.Config.GitBranch,
-			HeadBranch:  branchName,
-			BaseRepo:    a.Config.GerritConfig.Project,
-			HeadRepo:    a.Config.GerritConfig.Project,
-		}
-
-		pr, err := a.gitInterface.CreatePullRequest(ctx, prOptions)
-		if err != nil {
-			return fmt.Errorf("failed to create pull request: %w", err)
-		}
-
-		a.logger.Info("Created pull request for task", slog.String("id", task.ID), slog.String("title", pr.Title), slog.String("pr_id", pr.ID))
-	}
-
-	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()
-}
-
-// formatPullRequestDescription formats the description for the pull request
-func (a *Agent) formatPullRequestDescription(task *tm.Task, solution string) string {
-	var content strings.Builder
-
-	content.WriteString(fmt.Sprintf("**Task ID:** %s\n", task.ID))
-	content.WriteString(fmt.Sprintf("**Title:** %s\n", task.Title))
-	content.WriteString(fmt.Sprintf("**Priority:** %s\n", task.Priority))
-
-	if task.Description != "" {
-		content.WriteString(fmt.Sprintf("**Description:** %s\n", task.Description))
-	}
-
-	if task.DueDate != nil {
-		content.WriteString(fmt.Sprintf("**Due Date:** %s\n", task.DueDate.Format("2006-01-02")))
-	}
-
-	content.WriteString("\n**Solution:**\n\n")
-	content.WriteString(solution)
-
-	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
deleted file mode 100644
index 6ec2adc..0000000
--- a/server/agent/agent_test.go
+++ /dev/null
@@ -1,421 +0,0 @@
-package agent
-
-import (
-	"context"
-	"log/slog"
-	"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 logger for testing
-	logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}))
-
-	// Create task manager
-	taskManager := git_tm.NewGitTaskManagerWithLogger(gitInterface, tasksDir, logger)
-
-	// 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 logger for testing
-	logger = slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}))
-
-	// Create agent with mock LLM provider
-	agent := &Agent{
-		Config:       config,
-		llmProvider:  &MockLLMProvider{},
-		gitInterface: git.DefaultGit(codeRepoDir),
-		ctx:          context.Background(),
-		cancel:       func() {},
-		logger:       logger,
-	}
-
-	cleanup := func() {
-		agent.Stop()
-		os.RemoveAll(tempDir)
-	}
-
-	return agent, cleanup
-}
-
-func TestNewAgent(t *testing.T) {
-	// Create temporary directories
-	tempDir, err := os.MkdirTemp("", "agent-test")
-	require.NoError(t, err)
-	defer os.RemoveAll(tempDir)
-
-	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 logger for testing
-	logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}))
-
-	// Create task manager
-	taskManager := git_tm.NewGitTaskManagerWithLogger(gitInterface, tasksDir, logger)
-
-	// 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 logger for testing
-	logger = slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}))
-
-	// Create agent using NewAgent function
-	agent, err := NewAgent(config, logger)
-	require.NoError(t, err)
-	defer agent.Stop()
-
-	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
deleted file mode 100644
index a7faea9..0000000
--- a/server/agent/example.go
+++ /dev/null
@@ -1,205 +0,0 @@
-package agent
-
-import (
-	"context"
-	"log/slog"
-	"os"
-	"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 logger
-	logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}))
-
-	// Create git interface for task management
-	gitInterface := git.DefaultGit("./tasks-repo")
-
-	// Create task manager
-	taskManager := git_tm.NewGitTaskManagerWithLogger(gitInterface, "./tasks-repo", logger)
-
-	// 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, logger)
-	if err != nil {
-		logger.Error("Failed to create agent", slog.String("error", err.Error()))
-		os.Exit(1)
-	}
-
-	// 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 {
-		logger.Error("Failed to create task", slog.String("error", err.Error()))
-		os.Exit(1)
-	}
-
-	logger.Info("Created task", slog.String("id", task.ID))
-
-	// Run the agent (this will process tasks in an infinite loop)
-	go func() {
-		if err := agent.Run(); err != nil {
-			logger.Error("Agent stopped with error", slog.String("error", err.Error()))
-		}
-	}()
-
-	// 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 logger
-	logger := slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelInfo}))
-
-	// Create shared git interface for task management
-	gitInterface := git.DefaultGit("./tasks-repo")
-	taskManager := git_tm.NewGitTaskManagerWithLogger(gitInterface, "./tasks-repo", logger)
-
-	// 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, logger)
-		if err != nil {
-			logger.Error("Failed to create agent", slog.String("name", config.Name), slog.String("error", err.Error()))
-			continue
-		}
-
-		go func(agent *Agent, name string) {
-			logger.Info("Starting agent", slog.String("name", name))
-			if err := agent.Run(); err != nil {
-				logger.Error("Agent stopped with error", slog.String("name", name), slog.String("error", err.Error()))
-			}
-		}(agent, config.Name)
-	}
-
-	// Let agents run for a while
-	time.Sleep(10 * time.Minute)
-}
diff --git a/server/agent/simple_manager.go b/server/agent/manager.go
similarity index 67%
rename from server/agent/simple_manager.go
rename to server/agent/manager.go
index 27d522d..2d31b87 100644
--- a/server/agent/simple_manager.go
+++ b/server/agent/manager.go
@@ -14,24 +14,14 @@
 	"github.com/iomodo/staff/config"
 	"github.com/iomodo/staff/git"
 	"github.com/iomodo/staff/llm"
+	_ "github.com/iomodo/staff/llm/providers" // Auto-register all providers
 	"github.com/iomodo/staff/tm"
 )
 
-// SimpleAgent represents a simplified AI agent for MVP
-type SimpleAgent struct {
-	Name         string
-	Role         string
-	Model        string
-	SystemPrompt string
-	Provider     llm.LLMProvider
-	MaxTokens    *int
-	Temperature  *float64
-}
-
-// SimpleAgentManager manages multiple AI agents with basic Git operations
-type SimpleAgentManager struct {
+// Manager manages multiple AI agents with Git operations and task processing
+type Manager struct {
 	config       *config.Config
-	agents       map[string]*SimpleAgent
+	agents       map[string]*Agent
 	taskManager  tm.TaskManager
 	autoAssigner *assignment.AutoAssigner
 	prProvider   git.PullRequestProvider
@@ -40,8 +30,8 @@
 	stopChannels map[string]chan struct{}
 }
 
-// NewSimpleAgentManager creates a simplified agent manager
-func NewSimpleAgentManager(cfg *config.Config, taskManager tm.TaskManager) (*SimpleAgentManager, error) {
+// NewManager creates a new agent manager
+func NewManager(cfg *config.Config, taskManager tm.TaskManager) (*Manager, error) {
 	// Create auto-assigner
 	autoAssigner := assignment.NewAutoAssigner(cfg.Agents)
 
@@ -56,9 +46,9 @@
 	workspacePath := filepath.Join(".", "workspace")
 	cloneManager := git.NewCloneManager(repoURL, workspacePath)
 
-	manager := &SimpleAgentManager{
+	manager := &Manager{
 		config:       cfg,
-		agents:       make(map[string]*SimpleAgent),
+		agents:       make(map[string]*Agent),
 		taskManager:  taskManager,
 		autoAssigner: autoAssigner,
 		prProvider:   prProvider,
@@ -76,21 +66,21 @@
 }
 
 // initializeAgents creates agent instances from configuration
-func (am *SimpleAgentManager) initializeAgents() error {
-	for _, agentConfig := range am.config.Agents {
-		agent, err := am.createAgent(agentConfig)
+func (m *Manager) initializeAgents() error {
+	for _, agentConfig := range m.config.Agents {
+		agent, err := m.createAgent(agentConfig)
 		if err != nil {
 			return fmt.Errorf("failed to create agent %s: %w", agentConfig.Name, err)
 		}
-		am.agents[agentConfig.Name] = agent
+		m.agents[agentConfig.Name] = agent
 	}
 	return nil
 }
 
 // createAgent creates a single agent instance
-func (am *SimpleAgentManager) createAgent(agentConfig config.AgentConfig) (*SimpleAgent, error) {
+func (m *Manager) createAgent(agentConfig config.AgentConfig) (*Agent, error) {
 	// Load system prompt
-	systemPrompt, err := am.loadSystemPrompt(agentConfig.SystemPromptFile)
+	systemPrompt, err := m.loadSystemPrompt(agentConfig.SystemPromptFile)
 	if err != nil {
 		return nil, fmt.Errorf("failed to load system prompt: %w", err)
 	}
@@ -98,17 +88,17 @@
 	// Create LLM provider
 	llmConfig := llm.Config{
 		Provider: llm.ProviderOpenAI,
-		APIKey:   am.config.OpenAI.APIKey,
-		BaseURL:  am.config.OpenAI.BaseURL,
-		Timeout:  am.config.OpenAI.Timeout,
+		APIKey:   m.config.OpenAI.APIKey,
+		BaseURL:  m.config.OpenAI.BaseURL,
+		Timeout:  m.config.OpenAI.Timeout,
 	}
-	
+
 	provider, err := llm.CreateProvider(llmConfig)
 	if err != nil {
 		return nil, fmt.Errorf("failed to create LLM provider: %w", err)
 	}
 
-	agent := &SimpleAgent{
+	agent := &Agent{
 		Name:         agentConfig.Name,
 		Role:         agentConfig.Role,
 		Model:        agentConfig.Model,
@@ -116,13 +106,14 @@
 		Provider:     provider,
 		MaxTokens:    agentConfig.MaxTokens,
 		Temperature:  agentConfig.Temperature,
+		Stats:        AgentStats{},
 	}
 
 	return agent, nil
 }
 
 // loadSystemPrompt loads the system prompt from file
-func (am *SimpleAgentManager) loadSystemPrompt(filePath string) (string, error) {
+func (m *Manager) loadSystemPrompt(filePath string) (string, error) {
 	content, err := os.ReadFile(filePath)
 	if err != nil {
 		return "", fmt.Errorf("failed to read system prompt file %s: %w", filePath, err)
@@ -131,42 +122,42 @@
 }
 
 // StartAgent starts an agent to process tasks in a loop
-func (am *SimpleAgentManager) StartAgent(agentName string, loopInterval time.Duration) error {
-	agent, exists := am.agents[agentName]
+func (m *Manager) StartAgent(agentName string, loopInterval time.Duration) error {
+	agent, exists := m.agents[agentName]
 	if !exists {
 		return fmt.Errorf("agent %s not found", agentName)
 	}
 
-	if am.isRunning[agentName] {
+	if m.isRunning[agentName] {
 		return fmt.Errorf("agent %s is already running", agentName)
 	}
 
 	stopChan := make(chan struct{})
-	am.stopChannels[agentName] = stopChan
-	am.isRunning[agentName] = true
+	m.stopChannels[agentName] = stopChan
+	m.isRunning[agentName] = true
 
-	go am.runAgentLoop(agent, loopInterval, stopChan)
-	
+	go m.runAgentLoop(agent, loopInterval, stopChan)
+
 	log.Printf("Started agent %s (%s) with %s model", agentName, agent.Role, agent.Model)
 	return nil
 }
 
 // StopAgent stops a running agent
-func (am *SimpleAgentManager) StopAgent(agentName string) error {
-	if !am.isRunning[agentName] {
+func (m *Manager) StopAgent(agentName string) error {
+	if !m.isRunning[agentName] {
 		return fmt.Errorf("agent %s is not running", agentName)
 	}
 
-	close(am.stopChannels[agentName])
-	delete(am.stopChannels, agentName)
-	am.isRunning[agentName] = false
+	close(m.stopChannels[agentName])
+	delete(m.stopChannels, agentName)
+	m.isRunning[agentName] = false
 
 	log.Printf("Stopped agent %s", agentName)
 	return nil
 }
 
 // runAgentLoop runs the main processing loop for an agent
-func (am *SimpleAgentManager) runAgentLoop(agent *SimpleAgent, interval time.Duration, stopChan <-chan struct{}) {
+func (m *Manager) runAgentLoop(agent *Agent, interval time.Duration, stopChan <-chan struct{}) {
 	ticker := time.NewTicker(interval)
 	defer ticker.Stop()
 
@@ -176,7 +167,7 @@
 			log.Printf("Agent %s stopping", agent.Name)
 			return
 		case <-ticker.C:
-			if err := am.processAgentTasks(agent); err != nil {
+			if err := m.processAgentTasks(agent); err != nil {
 				log.Printf("Error processing tasks for agent %s: %v", agent.Name, err)
 			}
 		}
@@ -184,22 +175,36 @@
 }
 
 // processAgentTasks processes all assigned tasks for an agent
-func (am *SimpleAgentManager) processAgentTasks(agent *SimpleAgent) error {
+func (m *Manager) processAgentTasks(agent *Agent) error {
+	if agent.CurrentTask != nil {
+		return nil
+	}
+
 	// Get tasks assigned to this agent
-	tasks, err := am.taskManager.GetTasksByAssignee(agent.Name)
+	tasks, err := m.taskManager.GetTasksByAssignee(agent.Name)
 	if err != nil {
 		return fmt.Errorf("failed to get tasks for agent %s: %w", agent.Name, err)
 	}
 
+	log.Printf("Processing %d tasks for agent %s", len(tasks), agent.Name)
+
 	for _, task := range tasks {
-		if task.Status == tm.StatusPending || task.Status == tm.StatusInProgress {
-			if err := am.processTask(agent, task); err != nil {
+		if task.Status == tm.StatusToDo || task.Status == tm.StatusPending {
+			if err := m.processTask(agent, task); err != nil {
 				log.Printf("Error processing task %s: %v", task.ID, err)
 				// Mark task as failed
 				task.Status = tm.StatusFailed
-				if err := am.taskManager.UpdateTask(task); err != nil {
+				if err := m.taskManager.UpdateTask(task); err != nil {
 					log.Printf("Error updating failed task %s: %v", task.ID, err)
 				}
+				agent.Stats.TasksFailed++
+			} else {
+				agent.Stats.TasksCompleted++
+			}
+			// Update success rate
+			total := agent.Stats.TasksCompleted + agent.Stats.TasksFailed
+			if total > 0 {
+				agent.Stats.SuccessRate = float64(agent.Stats.TasksCompleted) / float64(total) * 100
 			}
 		}
 	}
@@ -208,31 +213,33 @@
 }
 
 // processTask processes a single task with an agent
-func (am *SimpleAgentManager) processTask(agent *SimpleAgent, task *tm.Task) error {
+func (m *Manager) processTask(agent *Agent, task *tm.Task) error {
 	ctx := context.Background()
+	startTime := time.Now()
 
 	log.Printf("Agent %s processing task %s: %s", agent.Name, task.ID, task.Title)
 
 	// Mark task as in progress
 	task.Status = tm.StatusInProgress
-	if err := am.taskManager.UpdateTask(task); err != nil {
+	agent.CurrentTask = &task.ID
+	if err := m.taskManager.UpdateTask(task); err != nil {
 		return fmt.Errorf("failed to update task status: %w", err)
 	}
 
 	// Generate solution using LLM
-	solution, err := am.generateSolution(ctx, agent, task)
+	solution, err := m.generateSolution(ctx, agent, task)
 	if err != nil {
 		return fmt.Errorf("failed to generate solution: %w", err)
 	}
 
 	// Create Git branch and commit solution
-	branchName := am.generateBranchName(task)
-	if err := am.createAndCommitSolution(branchName, task, solution, agent); err != nil {
+	branchName := m.generateBranchName(task)
+	if err := m.createAndCommitSolution(branchName, task, solution, agent); err != nil {
 		return fmt.Errorf("failed to commit solution: %w", err)
 	}
 
 	// Create pull request
-	prURL, err := am.createPullRequest(ctx, task, solution, agent, branchName)
+	prURL, err := m.createPullRequest(ctx, task, solution, agent, branchName)
 	if err != nil {
 		return fmt.Errorf("failed to create pull request: %w", err)
 	}
@@ -241,20 +248,29 @@
 	task.Status = tm.StatusCompleted
 	task.Solution = solution
 	task.PullRequestURL = prURL
-	task.CompletedAt = &time.Time{}
-	*task.CompletedAt = time.Now()
+	completedAt := time.Now()
+	task.CompletedAt = &completedAt
+	agent.CurrentTask = nil
 
-	if err := am.taskManager.UpdateTask(task); err != nil {
+	if err := m.taskManager.UpdateTask(task); err != nil {
 		return fmt.Errorf("failed to update completed task: %w", err)
 	}
 
-	log.Printf("Task %s completed by agent %s. PR: %s", task.ID, agent.Name, prURL)
+	// Update agent stats
+	duration := time.Since(startTime)
+	if agent.Stats.AvgTime == 0 {
+		agent.Stats.AvgTime = duration.Milliseconds()
+	} else {
+		agent.Stats.AvgTime = (agent.Stats.AvgTime + duration.Milliseconds()) / 2
+	}
+
+	log.Printf("Task %s completed by agent %s in %v. PR: %s", task.ID, agent.Name, duration, prURL)
 	return nil
 }
 
 // generateSolution uses the agent's LLM to generate a solution
-func (am *SimpleAgentManager) generateSolution(ctx context.Context, agent *SimpleAgent, task *tm.Task) (string, error) {
-	prompt := am.buildTaskPrompt(task)
+func (m *Manager) generateSolution(ctx context.Context, agent *Agent, task *tm.Task) (string, error) {
+	prompt := m.buildTaskPrompt(task)
 
 	req := llm.ChatCompletionRequest{
 		Model: agent.Model,
@@ -285,7 +301,7 @@
 }
 
 // buildTaskPrompt creates a detailed prompt for the LLM
-func (am *SimpleAgentManager) buildTaskPrompt(task *tm.Task) string {
+func (m *Manager) buildTaskPrompt(task *tm.Task) string {
 	return fmt.Sprintf(`Task: %s
 
 Priority: %s
@@ -305,7 +321,7 @@
 }
 
 // generateBranchName creates a Git branch name for the task
-func (am *SimpleAgentManager) generateBranchName(task *tm.Task) string {
+func (m *Manager) generateBranchName(task *tm.Task) string {
 	// Clean title for use in branch name
 	cleanTitle := strings.ToLower(task.Title)
 	cleanTitle = strings.ReplaceAll(cleanTitle, " ", "-")
@@ -318,30 +334,29 @@
 		}
 	}
 	cleanTitle = result.String()
-	
+
 	// Limit length
 	if len(cleanTitle) > 40 {
 		cleanTitle = cleanTitle[:40]
 	}
-	
-	return fmt.Sprintf("%s%s-%s", am.config.Git.BranchPrefix, task.ID, cleanTitle)
+
+	return fmt.Sprintf("%s%s-%s", m.config.Git.BranchPrefix, task.ID, cleanTitle)
 }
 
 // createAndCommitSolution creates a Git branch and commits the solution using per-agent clones
-// Each agent works in its own Git clone, eliminating concurrency issues
-func (am *SimpleAgentManager) createAndCommitSolution(branchName string, task *tm.Task, solution string, agent *SimpleAgent) error {
+func (m *Manager) createAndCommitSolution(branchName string, task *tm.Task, solution string, agent *Agent) error {
 	ctx := context.Background()
-	
+
 	// Get agent's dedicated Git clone
-	clonePath, err := am.cloneManager.GetAgentClonePath(agent.Name)
+	clonePath, err := m.cloneManager.GetAgentClonePath(agent.Name)
 	if err != nil {
 		return fmt.Errorf("failed to get agent clone: %w", err)
 	}
-	
+
 	log.Printf("Agent %s working in clone: %s", agent.Name, clonePath)
 
 	// Refresh the clone with latest changes
-	if err := am.cloneManager.RefreshAgentClone(agent.Name); err != nil {
+	if err := m.cloneManager.RefreshAgentClone(agent.Name); err != nil {
 		log.Printf("Warning: Failed to refresh clone for agent %s: %v", agent.Name, err)
 	}
 
@@ -401,7 +416,7 @@
 	}
 
 	// Commit changes
-	commitMsg := am.buildCommitMessage(task, agent)
+	commitMsg := m.buildCommitMessage(task, agent)
 	cmd = gitCmd("commit", "-m", commitMsg)
 	if err := cmd.Run(); err != nil {
 		return fmt.Errorf("failed to commit: %w", err)
@@ -418,9 +433,9 @@
 }
 
 // buildCommitMessage creates a commit message from template
-func (am *SimpleAgentManager) buildCommitMessage(task *tm.Task, agent *SimpleAgent) string {
-	template := am.config.Git.CommitMessageTemplate
-	
+func (m *Manager) buildCommitMessage(task *tm.Task, agent *Agent) string {
+	template := m.config.Git.CommitMessageTemplate
+
 	replacements := map[string]string{
 		"{task_id}":    task.ID,
 		"{task_title}": task.Title,
@@ -437,12 +452,12 @@
 }
 
 // createPullRequest creates a GitHub pull request
-func (am *SimpleAgentManager) createPullRequest(ctx context.Context, task *tm.Task, solution string, agent *SimpleAgent, branchName string) (string, error) {
+func (m *Manager) createPullRequest(ctx context.Context, task *tm.Task, solution string, agent *Agent, branchName string) (string, error) {
 	title := fmt.Sprintf("Task %s: %s", task.ID, task.Title)
-	
+
 	// Build PR description from template
-	description := am.buildPRDescription(task, solution, agent)
-	
+	description := m.buildPRDescription(task, solution, agent)
+
 	options := git.PullRequestOptions{
 		Title:       title,
 		Description: description,
@@ -452,24 +467,24 @@
 		Draft:       false,
 	}
 
-	pr, err := am.prProvider.CreatePullRequest(ctx, options)
+	pr, err := m.prProvider.CreatePullRequest(ctx, options)
 	if err != nil {
 		return "", fmt.Errorf("failed to create PR: %w", err)
 	}
 
-	return fmt.Sprintf("https://github.com/%s/%s/pull/%d", am.config.GitHub.Owner, am.config.GitHub.Repo, pr.Number), nil
+	return fmt.Sprintf("https://github.com/%s/%s/pull/%d", m.config.GitHub.Owner, m.config.GitHub.Repo, pr.Number), nil
 }
 
 // buildPRDescription creates PR description from template
-func (am *SimpleAgentManager) buildPRDescription(task *tm.Task, solution string, agent *SimpleAgent) string {
-	template := am.config.Git.PRTemplate
-	
+func (m *Manager) buildPRDescription(task *tm.Task, solution string, agent *Agent) string {
+	template := m.config.Git.PRTemplate
+
 	// Truncate solution for PR if too long
 	truncatedSolution := solution
 	if len(solution) > 1000 {
 		truncatedSolution = solution[:1000] + "...\n\n*See solution file for complete details*"
 	}
-	
+
 	replacements := map[string]string{
 		"{task_id}":          task.ID,
 		"{task_title}":       task.Title,
@@ -489,77 +504,80 @@
 }
 
 // AutoAssignTask automatically assigns a task to the best matching agent
-func (am *SimpleAgentManager) AutoAssignTask(taskID string) error {
-	task, err := am.taskManager.GetTask(taskID)
+func (m *Manager) AutoAssignTask(taskID string) error {
+	task, err := m.taskManager.GetTask(taskID)
 	if err != nil {
 		return fmt.Errorf("failed to get task: %w", err)
 	}
 
-	agentName, err := am.autoAssigner.AssignTask(task)
+	agentName, err := m.autoAssigner.AssignTask(task)
 	if err != nil {
 		return fmt.Errorf("failed to auto-assign task: %w", err)
 	}
 
 	task.Assignee = agentName
-	if err := am.taskManager.UpdateTask(task); err != nil {
+	if err := m.taskManager.UpdateTask(task); err != nil {
 		return fmt.Errorf("failed to update task assignment: %w", err)
 	}
 
-	explanation := am.autoAssigner.GetRecommendationExplanation(task, agentName)
+	explanation := m.autoAssigner.GetRecommendationExplanation(task, agentName)
 	log.Printf("Auto-assigned task %s to %s: %s", taskID, agentName, explanation)
 
 	return nil
 }
 
 // GetAgentStatus returns the status of all agents
-func (am *SimpleAgentManager) GetAgentStatus() map[string]SimpleAgentStatus {
-	status := make(map[string]SimpleAgentStatus)
-	
-	for name, agent := range am.agents {
-		status[name] = SimpleAgentStatus{
-			Name:      agent.Name,
-			Role:      agent.Role,
-			Model:     agent.Model,
-			IsRunning: am.isRunning[name],
+func (m *Manager) GetAgentStatus() map[string]AgentInfo {
+	status := make(map[string]AgentInfo)
+
+	for name, agent := range m.agents {
+		agentStatus := StatusIdle
+		if m.isRunning[name] {
+			if agent.CurrentTask != nil {
+				agentStatus = StatusRunning
+			}
+		} else {
+			agentStatus = StatusStopped
+		}
+
+		status[name] = AgentInfo{
+			Name:        agent.Name,
+			Role:        agent.Role,
+			Model:       agent.Model,
+			Status:      agentStatus,
+			CurrentTask: agent.CurrentTask,
+			Stats:       agent.Stats,
 		}
 	}
-	
+
 	return status
 }
 
-// SimpleAgentStatus represents the status of an agent
-type SimpleAgentStatus struct {
-	Name      string `json:"name"`
-	Role      string `json:"role"`
-	Model     string `json:"model"`
-	IsRunning bool   `json:"is_running"`
-}
-
 // IsAgentRunning checks if an agent is currently running
-func (am *SimpleAgentManager) IsAgentRunning(agentName string) bool {
-	return am.isRunning[agentName]
+func (m *Manager) IsAgentRunning(agentName string) bool {
+	return m.isRunning[agentName]
 }
 
 // Close shuts down the agent manager
-func (am *SimpleAgentManager) Close() error {
+func (m *Manager) Close() error {
 	// Stop all running agents
-	for agentName := range am.isRunning {
-		if am.isRunning[agentName] {
-			am.StopAgent(agentName)
+	for agentName := range m.isRunning {
+		if m.isRunning[agentName] {
+			m.StopAgent(agentName)
 		}
 	}
 
 	// Close all LLM providers
-	for _, agent := range am.agents {
+	for _, agent := range m.agents {
 		if err := agent.Provider.Close(); err != nil {
 			log.Printf("Error closing provider for agent %s: %v", agent.Name, err)
 		}
 	}
 
 	// Cleanup all agent Git clones
-	if err := am.cloneManager.CleanupAllClones(); err != nil {
+	if err := m.cloneManager.CleanupAllClones(); err != nil {
 		log.Printf("Error cleaning up agent clones: %v", err)
 	}
 
 	return nil
-}
\ No newline at end of file
+}
diff --git a/server/agent/types.go b/server/agent/types.go
new file mode 100644
index 0000000..a9f021c
--- /dev/null
+++ b/server/agent/types.go
@@ -0,0 +1,53 @@
+package agent
+
+import (
+	"github.com/iomodo/staff/llm"
+)
+
+// Agent represents an AI agent that can process tasks autonomously
+type Agent struct {
+	// Identity
+	Name string
+	Role string
+
+	// LLM Configuration
+	Model        string
+	SystemPrompt string
+	MaxTokens    *int
+	Temperature  *float64
+
+	// Runtime
+	Provider    llm.LLMProvider
+	CurrentTask *string // Task ID currently being processed
+
+	// Statistics
+	Stats AgentStats
+}
+
+// AgentStats tracks agent performance metrics
+type AgentStats struct {
+	TasksCompleted int     `json:"tasks_completed"`
+	TasksFailed    int     `json:"tasks_failed"`
+	SuccessRate    float64 `json:"success_rate"`
+	AvgTime        int64   `json:"avg_completion_time_seconds"`
+}
+
+// AgentStatus represents the current state of an agent
+type AgentStatus string
+
+const (
+	StatusIdle    AgentStatus = "idle"
+	StatusRunning AgentStatus = "running"
+	StatusError   AgentStatus = "error"
+	StatusStopped AgentStatus = "stopped"
+)
+
+// AgentInfo provides status information about an agent
+type AgentInfo struct {
+	Name        string      `json:"name"`
+	Role        string      `json:"role"`
+	Model       string      `json:"model"`
+	Status      AgentStatus `json:"status"`
+	CurrentTask *string     `json:"current_task,omitempty"`
+	Stats       AgentStats  `json:"stats"`
+}