Refactor everything

Change-Id: Ic3a37c38cfecba943c91f6ae545ce1c5b551c0d5
diff --git a/server/agent/README.md b/server/agent/README.md
deleted file mode 100644
index 0015d90..0000000
--- a/server/agent/README.md
+++ /dev/null
@@ -1,298 +0,0 @@
-# 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
new file mode 100644
index 0000000..fba80b7
--- /dev/null
+++ b/server/agent/agent.go
@@ -0,0 +1,239 @@
+package agent
+
+import (
+	"context"
+	"fmt"
+	"log/slog"
+	"os"
+	"time"
+
+	"github.com/iomodo/staff/config"
+	"github.com/iomodo/staff/llm"
+	"github.com/iomodo/staff/tm"
+)
+
+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
+
+	IsRunning bool
+	StopChan  chan struct{}
+
+	logger *slog.Logger
+
+	taskManager tm.TaskManager
+	thinker     *Thinker
+}
+
+func NewAgent(agentConfig config.AgentConfig, llmConfig llm.Config, taskManager tm.TaskManager, agentRoles []string, logger *slog.Logger) (*Agent, error) {
+	// Load system prompt
+	systemPrompt, err := loadSystemPrompt(agentConfig.SystemPromptFile)
+	if err != nil {
+		return nil, fmt.Errorf("failed to load system prompt: %w", err)
+	}
+
+	provider, err := llm.CreateProvider(llmConfig)
+	if err != nil {
+		return nil, fmt.Errorf("failed to create LLM provider: %w", err)
+	}
+
+	thinker := NewThinker(provider, agentConfig.Model, systemPrompt, *agentConfig.MaxTokens, *agentConfig.Temperature, agentRoles, logger)
+
+	agent := &Agent{
+		Name:         agentConfig.Name,
+		Role:         agentConfig.Role,
+		Model:        agentConfig.Model,
+		SystemPrompt: systemPrompt,
+		Provider:     provider,
+		MaxTokens:    agentConfig.MaxTokens,
+		Temperature:  agentConfig.Temperature,
+		taskManager:  taskManager,
+		logger:       logger,
+		thinker:      thinker,
+	}
+
+	return agent, nil
+}
+
+// Start starts an agent to process tasks in a loop
+func (a *Agent) Start(loopInterval time.Duration) error {
+	if a.IsRunning {
+		return fmt.Errorf("agent %s is already running", a.Name)
+	}
+
+	a.IsRunning = true
+	a.StopChan = make(chan struct{})
+
+	go a.runLoop(loopInterval)
+
+	a.logger.Info("Started agent",
+		slog.String("name", a.Name),
+		slog.String("role", a.Role),
+		slog.String("model", a.Model))
+	return nil
+}
+
+func (a *Agent) Stop() {
+	close(a.StopChan)
+	a.IsRunning = false
+}
+
+func (a *Agent) runLoop(interval time.Duration) {
+	ticker := time.NewTicker(interval)
+	defer ticker.Stop()
+
+	for {
+		select {
+		case <-a.StopChan:
+			a.logger.Info("Agent stopping", slog.String("name", a.Name))
+			return
+		case <-ticker.C:
+			if err := a.processTasks(); err != nil {
+				a.logger.Error("Error processing tasks for agent",
+					slog.String("agent", a.Name),
+					slog.String("error", err.Error()))
+			}
+		}
+	}
+}
+
+// processAgentTasks processes all assigned tasks for an agent
+func (a *Agent) processTasks() error {
+	if a.CurrentTask != nil {
+		return nil
+	}
+
+	// Get tasks assigned to this agent
+	tasks, err := a.taskManager.GetTasksByAssignee(a.Name)
+	if err != nil {
+		return fmt.Errorf("failed to get tasks for agent %s: %w", a.Name, err)
+	}
+
+	a.logger.Info("Processing tasks for agent",
+		slog.Int("task_count", len(tasks)),
+		slog.String("agent", a.Name))
+
+	for _, task := range tasks {
+		if task.Status == tm.StatusToDo {
+			if err := a.processTask(task); err != nil {
+				a.logger.Error("Error processing task",
+					slog.String("task_id", task.ID),
+					slog.String("error", err.Error()))
+			}
+		}
+	}
+
+	return nil
+}
+
+// processTask processes a single task with an agent
+func (a *Agent) processTask(task *tm.Task) error {
+	ctx := context.Background()
+	startTime := time.Now()
+
+	a.logger.Info("Agent processing task",
+		slog.String("agent", a.Name),
+		slog.String("task_id", task.ID),
+		slog.String("title", task.Title))
+
+	// Mark task as in progress
+	task.Status = tm.StatusInProgress
+	a.CurrentTask = &task.ID
+
+	// Check if this task should generate subtasks (with LLM decision)
+	if a.thinker.ShouldGenerateSubtasks(task) {
+		err := a.processSubtask(ctx, task)
+		if err == nil {
+			a.logger.Info("Task converted to subtasks by agent using LLM analysis",
+				slog.String("task_id", task.ID),
+				slog.String("agent", a.Name))
+			return nil
+		}
+		a.logger.Error("Error processing subtask",
+			slog.String("task_id", task.ID),
+			slog.String("error", err.Error()))
+	}
+
+	err := a.processSolution(ctx, task)
+	if err != nil {
+		return fmt.Errorf("failed to process solution for task: %w", err)
+	}
+	duration := time.Since(startTime)
+	a.logger.Info("Task completed by agent",
+		slog.String("task_id", task.ID),
+		slog.String("agent", a.Name),
+		slog.Duration("duration", duration))
+	return nil
+}
+
+func (a *Agent) processSubtask(ctx context.Context, task *tm.Task) error {
+	a.logger.Info("LLM determined task should generate subtasks", slog.String("task_id", task.ID))
+	analysis, err := a.thinker.GenerateSubtasksForTask(ctx, task)
+	if err != nil {
+		return fmt.Errorf("failed to generate subtasks for task: %w", err)
+	}
+
+	solutionURL, err2 := a.taskManager.ProposeSubTasks(ctx, task, analysis)
+	if err2 != nil {
+		return fmt.Errorf("failed to propose subtasks for task: %w", err2)
+	}
+	task.SolutionURL = solutionURL
+
+	a.logger.Info("Generated subtask Solution for task",
+		slog.String("task_id", task.ID),
+		slog.String("solution_url", solutionURL))
+	a.logger.Info("Proposed subtasks and new agents for task",
+		slog.String("task_id", task.ID),
+		slog.Int("subtask_count", len(analysis.Subtasks)),
+		slog.Int("new_agent_count", len(analysis.AgentCreations)))
+
+	// Log proposed new agents if any
+	if len(analysis.AgentCreations) > 0 {
+		for _, agent := range analysis.AgentCreations {
+			a.logger.Info("Proposed new agent",
+				slog.String("role", agent.Role),
+				slog.Any("skills", agent.Skills))
+		}
+	}
+
+	return nil
+}
+
+func (a *Agent) processSolution(ctx context.Context, task *tm.Task) error {
+	solution, err := a.thinker.GenerateSolution(ctx, task)
+	if err != nil {
+		return fmt.Errorf("failed to generate solution: %w", err)
+	}
+
+	solutionURL, err := a.taskManager.ProposeSolution(ctx, task, solution, a.Name)
+	if err != nil {
+		return fmt.Errorf("failed to propose solution: %w", err)
+	}
+	task.SolutionURL = solutionURL
+
+	a.logger.Info("Generated Solution for task",
+		slog.String("task_id", task.ID),
+		slog.String("agent", a.Name),
+		slog.String("solution_url", solutionURL))
+	return nil
+}
+
+// loadSystemPrompt loads the system prompt from file
+func 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)
+	}
+	return string(content), nil
+}
diff --git a/server/agent/manager.go b/server/agent/manager.go
index 191d42e..ea7474d 100644
--- a/server/agent/manager.go
+++ b/server/agent/manager.go
@@ -1,17 +1,11 @@
 package agent
 
 import (
-	"context"
 	"fmt"
 	"log/slog"
-	"os"
-	"os/exec"
-	"path/filepath"
-	"strings"
 	"time"
 
 	"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/task"
@@ -20,70 +14,31 @@
 
 // Manager manages multiple AI agents with Git operations and task processing
 type Manager struct {
-	config         *config.Config
-	agents         map[string]*Agent
-	taskManager    tm.TaskManager
-	autoAssigner   *task.AutoAssigner
-	prProvider     git.PullRequestProvider
-	cloneManager   *git.CloneManager
-	subtaskService *task.SubtaskService
-	isRunning      map[string]bool
-	stopChannels   map[string]chan struct{}
-	logger         *slog.Logger
+	config       *config.Config
+	agents       map[string]*Agent
+	taskManager  tm.TaskManager
+	autoAssigner *task.AutoAssigner
+	isRunning    map[string]bool
+	roles        []string
+	logger       *slog.Logger
 }
 
 // NewManager creates a new agent manager
 func NewManager(cfg *config.Config, taskManager tm.TaskManager, logger *slog.Logger) (*Manager, error) {
-	if logger == nil {
-		logger = slog.Default()
-	}
-	// Create auto-assigner
 	autoAssigner := task.NewAutoAssigner(cfg.Agents)
 
-	// Create PR provider based on configuration
-	var prProvider git.PullRequestProvider
-	var repoURL string
-
-	switch cfg.GetPrimaryGitProvider() {
-	case "github":
-		githubConfig := git.GitHubConfig{
-			Token:  cfg.GitHub.Token,
-			Logger: logger,
-		}
-		prProvider = git.NewGitHubPullRequestProvider(cfg.GitHub.Owner, cfg.GitHub.Repo, githubConfig)
-		repoURL = fmt.Sprintf("https://github.com/%s/%s.git", cfg.GitHub.Owner, cfg.GitHub.Repo)
-		logger.Info("Using GitHub as pull request provider",
-			slog.String("owner", cfg.GitHub.Owner),
-			slog.String("repo", cfg.GitHub.Repo))
-	case "gerrit":
-		gerritConfig := git.GerritConfig{
-			Username: cfg.Gerrit.Username,
-			Password: cfg.Gerrit.Password,
-			BaseURL:  cfg.Gerrit.BaseURL,
-			Logger:   logger,
-		}
-		prProvider = git.NewGerritPullRequestProvider(cfg.Gerrit.Project, gerritConfig)
-		repoURL = fmt.Sprintf("%s/%s", cfg.Gerrit.BaseURL, cfg.Gerrit.Project)
-		logger.Info("Using Gerrit as pull request provider",
-			slog.String("base_url", cfg.Gerrit.BaseURL),
-			slog.String("project", cfg.Gerrit.Project))
-	default:
-		return nil, fmt.Errorf("no valid Git provider configured")
+	agentRoles := make([]string, 0, len(cfg.Agents))
+	for _, agentConfig := range cfg.Agents {
+		agentRoles = append(agentRoles, agentConfig.Role)
 	}
 
-	// Create clone manager for per-agent Git repositories
-	workspacePath := filepath.Join(".", "workspace")
-	cloneManager := git.NewCloneManager(repoURL, workspacePath)
-
 	manager := &Manager{
 		config:       cfg,
 		agents:       make(map[string]*Agent),
 		taskManager:  taskManager,
 		autoAssigner: autoAssigner,
-		prProvider:   prProvider,
-		cloneManager: cloneManager,
 		isRunning:    make(map[string]bool),
-		stopChannels: make(map[string]chan struct{}),
+		roles:        agentRoles,
 		logger:       logger,
 	}
 
@@ -92,18 +47,19 @@
 		return nil, fmt.Errorf("failed to initialize agents: %w", err)
 	}
 
-	// Initialize subtask service after agents are created
-	if err := manager.initializeSubtaskService(); err != nil {
-		return nil, fmt.Errorf("failed to initialize subtask service: %w", err)
-	}
-
 	return manager, nil
 }
 
 // initializeAgents creates agent instances from configuration
 func (m *Manager) initializeAgents() error {
+	llmConfig := llm.Config{
+		Provider: llm.ProviderFake, // Use fake provider for testing
+		APIKey:   m.config.OpenAI.APIKey,
+		BaseURL:  m.config.OpenAI.BaseURL,
+		Timeout:  m.config.OpenAI.Timeout,
+	}
 	for _, agentConfig := range m.config.Agents {
-		agent, err := m.createAgent(agentConfig)
+		agent, err := NewAgent(agentConfig, llmConfig, m.taskManager, m.roles, m.logger)
 		if err != nil {
 			return fmt.Errorf("failed to create agent %s: %w", agentConfig.Name, err)
 		}
@@ -112,95 +68,25 @@
 	return nil
 }
 
-// initializeSubtaskService creates the subtask service with available agent roles
-func (m *Manager) initializeSubtaskService() error {
-	// Get agent roles from configuration
-	agentRoles := make([]string, 0, len(m.config.Agents))
-	for _, agentConfig := range m.config.Agents {
-		agentRoles = append(agentRoles, agentConfig.Name)
+func (m *Manager) StartAllAgents() {
+	// Start all configured agents with a default loop interval
+	defaultInterval := 1 * time.Second
+
+	for _, a := range m.agents {
+		m.logger.Info("Starting agent",
+			slog.String("name", a.Name),
+			slog.String("role", a.Role),
+			slog.String("model", a.Model))
+		if err := a.Start(defaultInterval); err != nil {
+			m.logger.Error("Failed to start agent",
+				slog.String("agent", a.Name),
+				slog.String("error", err.Error()))
+			continue
+		}
+		m.isRunning[a.Name] = true
 	}
-
-	// Use the first agent's LLM provider for subtask analysis
-	if len(m.agents) == 0 {
-		return fmt.Errorf("no agents available for subtask service")
-	}
-
-	var firstAgent *Agent
-	for _, agent := range m.agents {
-		firstAgent = agent
-		break
-	}
-
-	// Get owner and repo for subtask service based on provider
-	var owner, repo string
-	switch m.config.GetPrimaryGitProvider() {
-	case "github":
-		owner = m.config.GitHub.Owner
-		repo = m.config.GitHub.Repo
-	case "gerrit":
-		owner = m.config.Gerrit.Project
-		repo = m.config.Gerrit.Project
-	}
-
-	m.subtaskService = task.NewSubtaskService(
-		firstAgent.Provider,
-		m.taskManager,
-		agentRoles,
-		m.prProvider,
-		owner,
-		repo,
-		m.cloneManager,
-		m.logger,
-	)
-
-	return nil
 }
 
-// createAgent creates a single agent instance
-func (m *Manager) createAgent(agentConfig config.AgentConfig) (*Agent, error) {
-	// Load system prompt
-	systemPrompt, err := m.loadSystemPrompt(agentConfig.SystemPromptFile)
-	if err != nil {
-		return nil, fmt.Errorf("failed to load system prompt: %w", err)
-	}
-
-	// Create LLM provider
-	llmConfig := llm.Config{
-		Provider: llm.ProviderFake, // Use fake provider for testing
-		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 := &Agent{
-		Name:         agentConfig.Name,
-		Role:         agentConfig.Role,
-		Model:        agentConfig.Model,
-		SystemPrompt: systemPrompt,
-		Provider:     provider,
-		MaxTokens:    agentConfig.MaxTokens,
-		Temperature:  agentConfig.Temperature,
-		Stats:        AgentStats{},
-	}
-
-	return agent, nil
-}
-
-// loadSystemPrompt loads the system prompt from file
-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)
-	}
-	return string(content), nil
-}
-
-// StartAgent starts an agent to process tasks in a loop
 func (m *Manager) StartAgent(agentName string, loopInterval time.Duration) error {
 	agent, exists := m.agents[agentName]
 	if !exists {
@@ -211,409 +97,28 @@
 		return fmt.Errorf("agent %s is already running", agentName)
 	}
 
-	stopChan := make(chan struct{})
-	m.stopChannels[agentName] = stopChan
+	agent.Start(loopInterval)
 	m.isRunning[agentName] = true
-
-	go m.runAgentLoop(agent, loopInterval, stopChan)
-
-	m.logger.Info("Started agent",
-		slog.String("name", agentName),
-		slog.String("role", agent.Role),
-		slog.String("model", agent.Model))
 	return nil
 }
 
 // StopAgent stops a running agent
 func (m *Manager) StopAgent(agentName string) error {
+	agent, exists := m.agents[agentName]
+	if !exists {
+		return fmt.Errorf("agent %s not found", agentName)
+	}
 	if !m.isRunning[agentName] {
 		return fmt.Errorf("agent %s is not running", agentName)
 	}
 
-	close(m.stopChannels[agentName])
-	delete(m.stopChannels, agentName)
+	agent.Stop()
 	m.isRunning[agentName] = false
 
 	m.logger.Info("Stopped agent", slog.String("name", agentName))
 	return nil
 }
 
-// runAgentLoop runs the main processing loop for an agent
-func (m *Manager) runAgentLoop(agent *Agent, interval time.Duration, stopChan <-chan struct{}) {
-	ticker := time.NewTicker(interval)
-	defer ticker.Stop()
-
-	for {
-		select {
-		case <-stopChan:
-			m.logger.Info("Agent stopping", slog.String("name", agent.Name))
-			return
-		case <-ticker.C:
-			if err := m.processAgentTasks(agent); err != nil {
-				m.logger.Error("Error processing tasks for agent",
-					slog.String("agent", agent.Name),
-					slog.String("error", err.Error()))
-			}
-		}
-	}
-}
-
-// processAgentTasks processes all assigned tasks for an agent
-func (m *Manager) processAgentTasks(agent *Agent) error {
-	if agent.CurrentTask != nil {
-		return nil
-	}
-
-	// Get tasks assigned to this agent
-	tasks, err := m.taskManager.GetTasksByAssignee(agent.Name)
-	if err != nil {
-		return fmt.Errorf("failed to get tasks for agent %s: %w", agent.Name, err)
-	}
-
-	m.logger.Info("Processing tasks for agent",
-		slog.Int("task_count", len(tasks)),
-		slog.String("agent", agent.Name))
-
-	for _, task := range tasks {
-		if task.Status == tm.StatusToDo {
-			if err := m.processTask(agent, task); err != nil {
-				m.logger.Error("Error processing task",
-					slog.String("task_id", task.ID),
-					slog.String("error", err.Error()))
-				// Mark task as failed
-				task.Status = tm.StatusFailed
-				if err := m.taskManager.UpdateTask(task); err != nil {
-					m.logger.Error("Error updating failed task",
-						slog.String("task_id", task.ID),
-						slog.String("error", err.Error()))
-				}
-				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
-			}
-		}
-	}
-
-	return nil
-}
-
-// processTask processes a single task with an agent
-func (m *Manager) processTask(agent *Agent, task *tm.Task) error {
-	ctx := context.Background()
-	startTime := time.Now()
-
-	m.logger.Info("Agent processing task",
-		slog.String("agent", agent.Name),
-		slog.String("task_id", task.ID),
-		slog.String("title", task.Title))
-
-	// Mark task as in progress
-	task.Status = tm.StatusInProgress
-	agent.CurrentTask = &task.ID
-
-	// Check if this task should generate subtasks (with LLM decision)
-	if m.shouldGenerateSubtasks(task) {
-		m.logger.Info("LLM determined task should generate subtasks", slog.String("task_id", task.ID))
-		if err := m.generateSubtasksForTask(ctx, task); err != nil {
-			m.logger.Warn("Failed to generate subtasks for task",
-				slog.String("task_id", task.ID),
-				slog.String("error", err.Error()))
-		} else {
-			m.logger.Info("Task converted to subtasks by agent using LLM analysis",
-				slog.String("task_id", task.ID),
-				slog.String("agent", agent.Name))
-			return nil
-		}
-	}
-
-	// Generate solution using LLM
-	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 := 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 := m.createPullRequest(ctx, task, solution, agent, branchName)
-	if err != nil {
-		return fmt.Errorf("failed to create pull request: %w", err)
-	}
-
-	// 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
-	}
-
-	m.logger.Info("Task completed by agent",
-		slog.String("task_id", task.ID),
-		slog.String("agent", agent.Name),
-		slog.Duration("duration", duration),
-		slog.String("pr_url", prURL))
-	return nil
-}
-
-// generateSolution uses the agent's LLM to generate a solution
-func (m *Manager) generateSolution(ctx context.Context, agent *Agent, task *tm.Task) (string, error) {
-	prompt := m.buildTaskPrompt(task)
-
-	req := llm.ChatCompletionRequest{
-		Model: agent.Model,
-		Messages: []llm.Message{
-			{
-				Role:    llm.RoleSystem,
-				Content: agent.SystemPrompt,
-			},
-			{
-				Role:    llm.RoleUser,
-				Content: prompt,
-			},
-		},
-		MaxTokens:   agent.MaxTokens,
-		Temperature: agent.Temperature,
-	}
-
-	resp, err := agent.Provider.ChatCompletion(ctx, req)
-	if err != nil {
-		return "", fmt.Errorf("LLM request failed: %w", err)
-	}
-
-	if len(resp.Choices) == 0 {
-		return "", fmt.Errorf("no response from LLM")
-	}
-
-	return resp.Choices[0].Message.Content, nil
-}
-
-// buildTaskPrompt creates a detailed prompt for the LLM
-func (m *Manager) buildTaskPrompt(task *tm.Task) string {
-	return fmt.Sprintf(`Task: %s
-
-Priority: %s
-Description: %s
-
-Please provide a complete solution for this task. Include:
-1. Detailed implementation plan
-2. Code changes needed (if applicable)
-3. Files to be created or modified
-4. Testing considerations
-5. Any dependencies or prerequisites
-
-Your response should be comprehensive and actionable.`,
-		task.Title,
-		task.Priority,
-		task.Description)
-}
-
-// generateBranchName creates a Git branch name for the task
-func (m *Manager) generateBranchName(task *tm.Task) string {
-	// Clean title for use in branch name
-	cleanTitle := strings.ToLower(task.Title)
-	cleanTitle = strings.ReplaceAll(cleanTitle, " ", "-")
-	cleanTitle = strings.ReplaceAll(cleanTitle, "/", "-")
-	// Remove special characters
-	var result strings.Builder
-	for _, r := range cleanTitle {
-		if (r >= 'a' && r <= 'z') || (r >= '0' && r <= '9') || r == '-' {
-			result.WriteRune(r)
-		}
-	}
-	cleanTitle = result.String()
-
-	// Limit length
-	if len(cleanTitle) > 40 {
-		cleanTitle = cleanTitle[:40]
-	}
-
-	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
-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 := m.cloneManager.GetAgentClonePath(agent.Name)
-	if err != nil {
-		return fmt.Errorf("failed to get agent clone: %w", err)
-	}
-
-	m.logger.Info("Agent working in clone",
-		slog.String("agent", agent.Name),
-		slog.String("clone_path", clonePath))
-
-	// Refresh the clone with latest changes
-	if err := m.cloneManager.RefreshAgentClone(agent.Name); err != nil {
-		m.logger.Warn("Failed to refresh clone for agent",
-			slog.String("agent", agent.Name),
-			slog.String("error", err.Error()))
-	}
-
-	// All Git operations use the agent's clone directory
-	gitCmd := func(args ...string) *exec.Cmd {
-		return exec.CommandContext(ctx, "git", append([]string{"-C", clonePath}, args...)...)
-	}
-
-	// Ensure we're on main branch before creating new branch
-	cmd := gitCmd("checkout", "main")
-	if err := cmd.Run(); err != nil {
-		// Try master branch if main doesn't exist
-		cmd = gitCmd("checkout", "master")
-		if err := cmd.Run(); err != nil {
-			return fmt.Errorf("failed to checkout main/master branch: %w", err)
-		}
-	}
-
-	// Create branch
-	cmd = gitCmd("checkout", "-b", branchName)
-	if err := cmd.Run(); err != nil {
-		return fmt.Errorf("failed to create branch: %w", err)
-	}
-
-	// Create solution file in agent's clone
-	solutionDir := filepath.Join(clonePath, "tasks", "solutions")
-	if err := os.MkdirAll(solutionDir, 0755); err != nil {
-		return fmt.Errorf("failed to create solution directory: %w", err)
-	}
-
-	solutionFile := filepath.Join(solutionDir, fmt.Sprintf("%s-solution.md", task.ID))
-	solutionContent := fmt.Sprintf(`# Solution for Task: %s
-
-**Agent:** %s (%s)  
-**Model:** %s  
-**Completed:** %s
-
-## Task Description
-%s
-
-## Solution
-%s
-
----
-*Generated by Staff AI Agent System*
-`, task.Title, agent.Name, agent.Role, agent.Model, time.Now().Format(time.RFC3339), task.Description, solution)
-
-	if err := os.WriteFile(solutionFile, []byte(solutionContent), 0644); err != nil {
-		return fmt.Errorf("failed to write solution file: %w", err)
-	}
-
-	// Stage files
-	relativeSolutionFile := filepath.Join("tasks", "solutions", fmt.Sprintf("%s-solution.md", task.ID))
-	cmd = gitCmd("add", relativeSolutionFile)
-	if err := cmd.Run(); err != nil {
-		return fmt.Errorf("failed to stage files: %w", err)
-	}
-
-	// Commit changes
-	commitMsg := m.buildCommitMessage(task, agent)
-	cmd = gitCmd("commit", "-m", commitMsg)
-	if err := cmd.Run(); err != nil {
-		return fmt.Errorf("failed to commit: %w", err)
-	}
-
-	// Push branch
-	cmd = gitCmd("push", "-u", "origin", branchName)
-	if err := cmd.Run(); err != nil {
-		return fmt.Errorf("failed to push branch: %w", err)
-	}
-
-	m.logger.Info("Agent successfully pushed branch",
-		slog.String("agent", agent.Name),
-		slog.String("branch", branchName))
-	return nil
-}
-
-// buildCommitMessage creates a commit message from template
-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,
-		"{agent_name}": agent.Name,
-		"{solution}":   "See solution file for details",
-	}
-
-	result := template
-	for placeholder, value := range replacements {
-		result = strings.ReplaceAll(result, placeholder, value)
-	}
-
-	return result
-}
-
-// createPullRequest creates a GitHub pull request
-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 := m.buildPRDescription(task, solution, agent)
-
-	options := git.PullRequestOptions{
-		Title:       title,
-		Description: description,
-		HeadBranch:  branchName,
-		BaseBranch:  "main",
-		Labels:      []string{"ai-generated", "staff-agent", strings.ToLower(agent.Role)},
-		Draft:       false,
-	}
-
-	pr, err := m.prProvider.CreatePullRequest(ctx, options)
-	if err != nil {
-		return "", fmt.Errorf("failed to create PR: %w", err)
-	}
-
-	// Generate provider-specific PR URL
-	switch m.config.GetPrimaryGitProvider() {
-	case "github":
-		return fmt.Sprintf("https://github.com/%s/%s/pull/%d", m.config.GitHub.Owner, m.config.GitHub.Repo, pr.Number), nil
-	case "gerrit":
-		return fmt.Sprintf("%s/c/%s/+/%d", m.config.Gerrit.BaseURL, m.config.Gerrit.Project, pr.Number), nil
-	default:
-		return "", fmt.Errorf("unknown git provider")
-	}
-}
-
-// buildPRDescription creates PR description from template
-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,
-		"{task_description}": task.Description,
-		"{agent_name}":       fmt.Sprintf("%s (%s)", agent.Name, agent.Role),
-		"{priority}":         string(task.Priority),
-		"{solution}":         truncatedSolution,
-		"{files_changed}":    fmt.Sprintf("- `tasks/solutions/%s-solution.md`", task.ID),
-	}
-
-	result := template
-	for placeholder, value := range replacements {
-		result = strings.ReplaceAll(result, placeholder, value)
-	}
-
-	return result
-}
-
 // AutoAssignTask automatically assigns a task to the best matching agent
 func (m *Manager) AutoAssignTask(taskID string) error {
 	task, err := m.taskManager.GetTask(taskID)
@@ -640,108 +145,6 @@
 	return nil
 }
 
-// GetAgentStatus returns the status of all agents
-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
-}
-
-// shouldGenerateSubtasks determines if a task should be broken down into subtasks using LLM
-func (m *Manager) shouldGenerateSubtasks(task *tm.Task) bool {
-	// Don't generate subtasks for subtasks
-	if task.ParentTaskID != "" {
-		return false
-	}
-
-	// Don't generate if already evaluated
-	if task.SubtasksEvaluated {
-		return false
-	}
-
-	// Ask LLM to decide
-	ctx := context.Background()
-	decision, err := m.subtaskService.ShouldGenerateSubtasks(ctx, task)
-	if err != nil {
-		m.logger.Warn("Failed to get LLM subtask decision for task",
-			slog.String("task_id", task.ID),
-			slog.String("error", err.Error()))
-		// Fallback to simple heuristics
-		return task.Priority == tm.PriorityHigh || len(task.Description) > 200
-	}
-
-	task.SubtasksEvaluated = true
-	m.logger.Info("LLM subtask decision for task",
-		slog.String("task_id", task.ID),
-		slog.Bool("needs_subtasks", decision.NeedsSubtasks),
-		slog.Int("complexity_score", decision.ComplexityScore),
-		slog.String("reasoning", decision.Reasoning))
-
-	return decision.NeedsSubtasks
-}
-
-// generateSubtasksForTask analyzes a task and creates a PR with proposed subtasks
-func (m *Manager) generateSubtasksForTask(ctx context.Context, task *tm.Task) error {
-	if m.subtaskService == nil {
-		return fmt.Errorf("subtask service not initialized")
-	}
-
-	// Analyze the task for subtasks
-	analysis, err := m.subtaskService.AnalyzeTaskForSubtasks(ctx, task)
-	if err != nil {
-		return fmt.Errorf("failed to analyze task for subtasks: %w", err)
-	}
-
-	// Generate a PR with the subtask proposals
-	prURL, err := m.subtaskService.GenerateSubtaskPR(ctx, analysis)
-	if err != nil {
-		return fmt.Errorf("failed to generate subtask PR: %w", err)
-	}
-
-	// Update the task with subtask information
-	task.SubtasksPRURL = prURL
-	task.SubtasksGenerated = true
-
-	m.logger.Info("Generated subtask PR for task",
-		slog.String("task_id", task.ID),
-		slog.String("pr_url", prURL))
-	m.logger.Info("Proposed subtasks and new agents for task",
-		slog.String("task_id", task.ID),
-		slog.Int("subtask_count", len(analysis.Subtasks)),
-		slog.Int("new_agent_count", len(analysis.AgentCreations)))
-
-	// Log proposed new agents if any
-	if len(analysis.AgentCreations) > 0 {
-		for _, agent := range analysis.AgentCreations {
-			m.logger.Info("Proposed new agent",
-				slog.String("role", agent.Role),
-				slog.Any("skills", agent.Skills))
-		}
-	}
-
-	return nil
-}
-
 // IsAgentRunning checks if an agent is currently running
 func (m *Manager) IsAgentRunning(agentName string) bool {
 	return m.isRunning[agentName]
@@ -764,18 +167,5 @@
 				slog.String("error", err.Error()))
 		}
 	}
-
-	// Cleanup all agent Git clones
-	if err := m.cloneManager.CleanupAllClones(); err != nil {
-		m.logger.Error("Error cleaning up agent clones", slog.String("error", err.Error()))
-	}
-
-	// Cleanup subtask service
-	if m.subtaskService != nil {
-		if err := m.subtaskService.Close(); err != nil {
-			m.logger.Error("Error closing subtask service", slog.String("error", err.Error()))
-		}
-	}
-
 	return nil
 }
diff --git a/server/agent/thinker.go b/server/agent/thinker.go
new file mode 100644
index 0000000..8e70230
--- /dev/null
+++ b/server/agent/thinker.go
@@ -0,0 +1,522 @@
+package agent
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"log/slog"
+	"strings"
+
+	"github.com/iomodo/staff/llm"
+	"github.com/iomodo/staff/tm"
+	"golang.org/x/text/cases"
+	"golang.org/x/text/language"
+)
+
+type Thinker struct {
+	roles        []string
+	llmProvider  llm.LLMProvider
+	model        string // TODO: abstract away in llmProvider
+	systemPrompt string // TODO abstract away in llmProvider
+	maxTokens    int
+	temperature  float64
+	logger       *slog.Logger
+}
+
+func NewThinker(llmProvider llm.LLMProvider, model string, systemPrompt string, maxTokens int, temperature float64, roles []string, logger *slog.Logger) *Thinker {
+	return &Thinker{llmProvider: llmProvider, model: model, maxTokens: maxTokens, temperature: temperature, roles: roles, logger: logger}
+}
+
+// shouldGenerateSubtasks determines if a task should be broken down into subtasks using LLM
+func (t *Thinker) ShouldGenerateSubtasks(task *tm.Task) bool {
+	// Don't generate subtasks for subtasks
+	if task.ParentTaskID != "" {
+		return false
+	}
+
+	// Don't generate if already evaluated
+	if task.SubtasksEvaluated {
+		return false
+	}
+
+	// Ask LLM to decide
+	ctx := context.Background()
+	decision, err := t.shouldGenerateSubtasks(ctx, task)
+	if err != nil {
+		t.logger.Warn("Failed to get LLM subtask decision for task",
+			slog.String("task_id", task.ID),
+			slog.String("error", err.Error()))
+		// Fallback to simple heuristics
+		return task.Priority == tm.PriorityHigh || len(task.Description) > 200
+	}
+
+	task.SubtasksEvaluated = true
+	t.logger.Info("LLM subtask decision for task",
+		slog.String("task_id", task.ID),
+		slog.Bool("needs_subtasks", decision.NeedsSubtasks),
+		slog.Int("complexity_score", decision.ComplexityScore),
+		slog.String("reasoning", decision.Reasoning))
+
+	return decision.NeedsSubtasks
+}
+
+// AnalyzeTaskForSubtasks uses LLM to analyze a task and propose subtasks
+func (t *Thinker) GenerateSubtasksForTask(ctx context.Context, task *tm.Task) (*tm.SubtaskAnalysis, error) {
+	prompt := buildSubtaskAnalysisPrompt(task)
+
+	req := llm.ChatCompletionRequest{
+		Model: t.model,
+		Messages: []llm.Message{
+			{
+				Role:    llm.RoleSystem,
+				Content: getSubtaskAnalysisSystemPrompt(t.roles),
+			},
+			{
+				Role:    llm.RoleUser,
+				Content: prompt,
+			},
+		},
+		MaxTokens:   &[]int{4000}[0],
+		Temperature: &[]float64{0.3}[0],
+	}
+
+	resp, err := t.llmProvider.ChatCompletion(ctx, req)
+	if err != nil {
+		return nil, fmt.Errorf("LLM analysis failed: %w", err)
+	}
+
+	if len(resp.Choices) == 0 {
+		return nil, fmt.Errorf("no response from LLM")
+	}
+
+	// Parse the LLM response
+	analysis, err := parseSubtaskAnalysis(resp.Choices[0].Message.Content, task.ID, t.roles, t.logger)
+	if err != nil {
+		return nil, fmt.Errorf("failed to parse LLM response: %w", err)
+	}
+
+	return analysis, nil
+}
+
+// generateSolution uses the agent's LLM to generate a solution
+func (t *Thinker) GenerateSolution(ctx context.Context, task *tm.Task) (string, error) {
+	prompt := buildTaskPrompt(task)
+
+	req := llm.ChatCompletionRequest{
+		Model: t.model,
+		Messages: []llm.Message{
+			{
+				Role:    llm.RoleSystem,
+				Content: t.systemPrompt,
+			},
+			{
+				Role:    llm.RoleUser,
+				Content: prompt,
+			},
+		},
+		MaxTokens:   &t.maxTokens,
+		Temperature: &t.temperature,
+	}
+
+	resp, err := t.llmProvider.ChatCompletion(ctx, req)
+	if err != nil {
+		return "", fmt.Errorf("LLM request failed: %w", err)
+	}
+
+	if len(resp.Choices) == 0 {
+		return "", fmt.Errorf("no response from LLM")
+	}
+
+	return resp.Choices[0].Message.Content, nil
+}
+
+// ShouldGenerateSubtasks asks LLM whether a task needs subtasks based on existing agents
+func (t *Thinker) shouldGenerateSubtasks(ctx context.Context, task *tm.Task) (*tm.SubtaskDecision, error) {
+	prompt := buildSubtaskDecisionPrompt(task)
+
+	req := llm.ChatCompletionRequest{
+		Model: t.model,
+		Messages: []llm.Message{
+			{
+				Role:    llm.RoleSystem,
+				Content: getSubtaskDecisionSystemPrompt(t.roles),
+			},
+			{
+				Role:    llm.RoleUser,
+				Content: prompt,
+			},
+		},
+		MaxTokens:   &[]int{1000}[0],
+		Temperature: &[]float64{0.3}[0],
+	}
+
+	resp, err := t.llmProvider.ChatCompletion(ctx, req)
+	if err != nil {
+		return nil, fmt.Errorf("LLM decision failed: %w", err)
+	}
+
+	if len(resp.Choices) == 0 {
+		return nil, fmt.Errorf("no response from LLM")
+	}
+
+	// Parse the LLM response
+	decision, err := parseSubtaskDecision(resp.Choices[0].Message.Content)
+	if err != nil {
+		return nil, fmt.Errorf("failed to parse LLM decision: %w", err)
+	}
+
+	return decision, nil
+}
+
+func buildSubtaskDecisionPrompt(task *tm.Task) string {
+	return fmt.Sprintf(`Please evaluate whether the following task needs to be broken down into subtasks:
+
+**Task Title:** %s
+
+**Description:** %s
+
+**Priority:** %s
+
+**Current Status:** %s
+
+Consider:
+- Can this be completed by a single agent with existing capabilities?
+- Does it require multiple specialized skills?
+- Is the scope too large for one person?
+- Are there logical components that could be parallelized?
+
+Provide your decision in the JSON format specified in the system prompt.`,
+		task.Title,
+		task.Description,
+		task.Priority,
+		task.Status)
+}
+
+func getSubtaskDecisionSystemPrompt(roles []string) string {
+	availableRoles := strings.Join(roles, ", ")
+
+	return fmt.Sprintf(`You are an expert project manager and task analyst. Your job is to determine whether a task needs to be broken down into subtasks.
+
+Currently available team roles and their capabilities: %s
+
+When evaluating a task, consider:
+1. Task complexity and scope
+2. Whether multiple specialized skills are needed
+3. If the task can be completed by a single agent with current capabilities
+4. Whether new agent roles might be needed for specialized skills
+
+Respond with a JSON object in this exact format:
+{
+  "needs_subtasks": true/false,
+  "reasoning": "Clear explanation of why subtasks are or aren't needed",
+  "complexity_score": 5,
+  "required_skills": ["skill1", "skill2", "skill3"]
+}
+
+Complexity score should be 1-10 where:
+- 1-3: Simple tasks that can be handled by one agent
+- 4-6: Moderate complexity, might benefit from subtasks
+- 7-10: Complex tasks that definitely need breaking down
+
+Required skills should list all technical/domain skills needed to complete the task.`, availableRoles)
+}
+
+func parseSubtaskDecision(response string) (*tm.SubtaskDecision, error) {
+	// Try to extract JSON from the response
+	jsonStart := strings.Index(response, "{")
+	jsonEnd := strings.LastIndex(response, "}")
+
+	if jsonStart == -1 || jsonEnd == -1 {
+		return nil, fmt.Errorf("no JSON found in LLM response")
+	}
+
+	jsonStr := response[jsonStart : jsonEnd+1]
+
+	var decision tm.SubtaskDecision
+	if err := json.Unmarshal([]byte(jsonStr), &decision); err != nil {
+		return nil, fmt.Errorf("failed to unmarshal JSON: %w", err)
+	}
+
+	return &decision, nil
+}
+
+func buildSubtaskAnalysisPrompt(task *tm.Task) string {
+	return fmt.Sprintf(`Please analyze the following task and break it down into subtasks:
+
+**Task Title:** %s
+
+**Description:** %s
+
+**Priority:** %s
+
+**Current Status:** %s
+
+Please analyze this task and provide a detailed breakdown into subtasks. Consider:
+- Technical complexity and requirements
+- Logical task dependencies 
+- Appropriate skill sets needed for each subtask
+- Risk factors and potential blockers
+- Estimated effort for each component
+
+Provide the analysis in the JSON format specified in the system prompt.`,
+		task.Title,
+		task.Description,
+		task.Priority,
+		task.Status)
+}
+
+func getSubtaskAnalysisSystemPrompt(roles []string) string {
+	availableRoles := strings.Join(roles, ", ")
+
+	return fmt.Sprintf(`You are an expert project manager and technical architect. Your job is to analyze complex tasks and break them down into well-defined subtasks that can be assigned to specialized team members.
+
+Currently available team roles: %s
+
+When analyzing a task, you should:
+1. Understand the task requirements and scope
+2. Break it down into logical, manageable subtasks
+3. Assign each subtask to the most appropriate team role OR propose creating new agents
+4. Estimate effort and identify dependencies
+5. Provide a clear execution strategy
+
+If you need specialized skills not covered by existing roles, propose new agent creation.
+
+Respond with a JSON object in this exact format:
+{
+  "analysis_summary": "Brief analysis of the task and approach",
+  "subtasks": [
+    {
+      "title": "Subtask title",
+      "description": "Detailed description of what needs to be done",
+      "priority": "high|medium|low",
+      "assigned_to": "role_name",
+      "estimated_hours": 8,
+      "dependencies": ["subtask_index_1", "subtask_index_2"],
+      "required_skills": ["skill1", "skill2"]
+    }
+  ],
+  "agent_creations": [
+    {
+      "role": "new_role_name",
+      "skills": ["specialized_skill1", "specialized_skill2"],
+      "description": "Description of what this agent does",
+      "justification": "Why this new agent is needed"
+    }
+  ],
+  "recommended_approach": "High-level strategy for executing these subtasks",
+  "estimated_total_hours": 40,
+  "risk_assessment": "Potential risks and mitigation strategies"
+}
+
+For existing roles, use: %s
+For new agents, propose appropriate role names and skill sets.
+Dependencies should reference subtask indices (e.g., ["0", "1"] means depends on first and second subtasks).`, availableRoles, availableRoles)
+}
+
+func parseSubtaskAnalysis(response string, parentTaskID string, agentRoles []string, logger *slog.Logger) (*tm.SubtaskAnalysis, error) {
+	// Try to extract JSON from the response (LLM might wrap it in markdown)
+	jsonStart := strings.Index(response, "{")
+	jsonEnd := strings.LastIndex(response, "}")
+
+	if jsonStart == -1 || jsonEnd == -1 {
+		return nil, fmt.Errorf("no JSON found in LLM response")
+	}
+
+	jsonStr := response[jsonStart : jsonEnd+1]
+
+	var rawAnalysis struct {
+		AnalysisSummary string `json:"analysis_summary"`
+		Subtasks        []struct {
+			Title          string   `json:"title"`
+			Description    string   `json:"description"`
+			Priority       string   `json:"priority"`
+			AssignedTo     string   `json:"assigned_to"`
+			EstimatedHours int      `json:"estimated_hours"`
+			Dependencies   []string `json:"dependencies"`
+			RequiredSkills []string `json:"required_skills"`
+		} `json:"subtasks"`
+		AgentCreations      []tm.AgentCreationProposal `json:"agent_creations"`
+		RecommendedApproach string                     `json:"recommended_approach"`
+		EstimatedTotalHours int                        `json:"estimated_total_hours"`
+		RiskAssessment      string                     `json:"risk_assessment"`
+	}
+
+	if err := json.Unmarshal([]byte(jsonStr), &rawAnalysis); err != nil {
+		return nil, fmt.Errorf("failed to unmarshal JSON: %w", err)
+	}
+
+	// Convert to our types
+	analysis := &tm.SubtaskAnalysis{
+		ParentTaskID:        parentTaskID,
+		AnalysisSummary:     rawAnalysis.AnalysisSummary,
+		AgentCreations:      rawAnalysis.AgentCreations,
+		RecommendedApproach: rawAnalysis.RecommendedApproach,
+		EstimatedTotalHours: rawAnalysis.EstimatedTotalHours,
+		RiskAssessment:      rawAnalysis.RiskAssessment,
+	}
+
+	// Convert subtasks
+	for _, st := range rawAnalysis.Subtasks {
+		priority := tm.PriorityMedium // default
+		switch strings.ToLower(st.Priority) {
+		case "high":
+			priority = tm.PriorityHigh
+		case "low":
+			priority = tm.PriorityLow
+		}
+
+		subtask := tm.SubtaskProposal{
+			Title:          st.Title,
+			Description:    st.Description,
+			Priority:       priority,
+			AssignedTo:     st.AssignedTo,
+			EstimatedHours: st.EstimatedHours,
+			Dependencies:   st.Dependencies,
+			RequiredSkills: st.RequiredSkills,
+		}
+
+		analysis.Subtasks = append(analysis.Subtasks, subtask)
+	}
+
+	// Validate agent assignments and handle new agent creation
+	if err := validateAndHandleAgentAssignments(analysis, agentRoles, logger); err != nil {
+		logger.Warn("Warning during agent assignment handling", slog.String("error", err.Error()))
+	}
+
+	return analysis, nil
+}
+
+func validateAndHandleAgentAssignments(analysis *tm.SubtaskAnalysis, agentRoles []string, logger *slog.Logger) error {
+	// Collect all agent roles that will be available (existing + proposed new ones)
+	availableRoles := make(map[string]bool)
+	for _, role := range agentRoles {
+		availableRoles[role] = true
+	}
+
+	// Add proposed new agent roles
+	for _, agentCreation := range analysis.AgentCreations {
+		availableRoles[agentCreation.Role] = true
+
+		// Create a subtask for agent creation
+		agentCreationSubtask := tm.SubtaskProposal{
+			Title:          fmt.Sprintf("Create %s Agent", cases.Title(language.English).String(agentCreation.Role)),
+			Description:    fmt.Sprintf("Create and configure a new %s agent with skills: %s. %s", agentCreation.Role, strings.Join(agentCreation.Skills, ", "), agentCreation.Justification),
+			Priority:       tm.PriorityHigh, // Agent creation is high priority
+			AssignedTo:     "ceo",           // CEO creates new agents
+			EstimatedHours: 4,               // Estimated time to set up new agent
+			Dependencies:   []string{},      // No dependencies for agent creation
+			RequiredSkills: []string{"agent_configuration", "system_design"},
+		}
+
+		// Insert at the beginning so agent creation happens first
+		analysis.Subtasks = append([]tm.SubtaskProposal{agentCreationSubtask}, analysis.Subtasks...)
+
+		// Update dependencies to account for the new subtask at index 0
+		for i := 1; i < len(analysis.Subtasks); i++ {
+			for j, dep := range analysis.Subtasks[i].Dependencies {
+				// Convert dependency index and increment by 1
+				if depIndex := parseDependencyIndex(dep); depIndex >= 0 {
+					analysis.Subtasks[i].Dependencies[j] = fmt.Sprintf("%d", depIndex+1)
+				}
+			}
+		}
+	}
+
+	// Now validate all assignments against available roles
+	defaultRole := "ceo" // fallback role
+	if len(agentRoles) > 0 {
+		defaultRole = agentRoles[0]
+	}
+
+	for i := range analysis.Subtasks {
+		if !availableRoles[analysis.Subtasks[i].AssignedTo] {
+			logger.Warn("Unknown agent role for subtask, using default",
+				slog.String("unknown_role", analysis.Subtasks[i].AssignedTo),
+				slog.String("subtask_title", analysis.Subtasks[i].Title),
+				slog.String("assigned_role", defaultRole))
+			analysis.Subtasks[i].AssignedTo = defaultRole
+		}
+	}
+
+	return nil
+}
+
+func parseDependencyIndex(dep string) int {
+	var idx int
+	if _, err := fmt.Sscanf(dep, "%d", &idx); err == nil {
+		return idx
+	}
+	return -1 // Invalid dependency format
+}
+
+func generateSubtaskPRContent(analysis *tm.SubtaskAnalysis) string {
+	var content strings.Builder
+
+	content.WriteString(fmt.Sprintf("# Subtasks Created for Task %s\n\n", analysis.ParentTaskID))
+	content.WriteString(fmt.Sprintf("This PR creates **%d individual task files** in `/operations/tasks/` ready for agent assignment.\n\n", len(analysis.Subtasks)))
+	content.WriteString(fmt.Sprintf("✅ **Parent task `%s` has been marked as completed** - the complex task has been successfully broken down into actionable subtasks.\n\n", analysis.ParentTaskID))
+	content.WriteString(fmt.Sprintf("## Analysis Summary\n%s\n\n", analysis.AnalysisSummary))
+	content.WriteString(fmt.Sprintf("## Recommended Approach\n%s\n\n", analysis.RecommendedApproach))
+	content.WriteString(fmt.Sprintf("**Estimated Total Hours:** %d\n\n", analysis.EstimatedTotalHours))
+
+	// List the created task files
+	content.WriteString("## Created Task Files\n\n")
+	for i, subtask := range analysis.Subtasks {
+		taskID := fmt.Sprintf("%s-subtask-%d", analysis.ParentTaskID, i+1)
+		content.WriteString(fmt.Sprintf("### %d. `%s.md`\n", i+1, taskID))
+		content.WriteString(fmt.Sprintf("- **Title:** %s\n", subtask.Title))
+		content.WriteString(fmt.Sprintf("- **Assigned to:** %s\n", subtask.AssignedTo))
+		content.WriteString(fmt.Sprintf("- **Priority:** %s\n", subtask.Priority))
+		content.WriteString(fmt.Sprintf("- **Estimated Hours:** %d\n", subtask.EstimatedHours))
+		content.WriteString(fmt.Sprintf("- **Description:** %s\n\n", subtask.Description))
+	}
+
+	if analysis.RiskAssessment != "" {
+		content.WriteString(fmt.Sprintf("## Risk Assessment\n%s\n\n", analysis.RiskAssessment))
+	}
+
+	content.WriteString("## Proposed Subtasks\n\n")
+
+	for i, subtask := range analysis.Subtasks {
+		content.WriteString(fmt.Sprintf("### %d. %s\n", i+1, subtask.Title))
+		content.WriteString(fmt.Sprintf("- **Assigned to:** %s\n", subtask.AssignedTo))
+		content.WriteString(fmt.Sprintf("- **Priority:** %s\n", subtask.Priority))
+		content.WriteString(fmt.Sprintf("- **Estimated Hours:** %d\n", subtask.EstimatedHours))
+
+		if len(subtask.Dependencies) > 0 {
+			deps := strings.Join(subtask.Dependencies, ", ")
+			content.WriteString(fmt.Sprintf("- **Dependencies:** %s\n", deps))
+		}
+
+		content.WriteString(fmt.Sprintf("- **Description:** %s\n\n", subtask.Description))
+	}
+
+	content.WriteString("---\n")
+	content.WriteString("*Generated by Staff AI Agent System*\n\n")
+	content.WriteString("**Instructions:**\n")
+	content.WriteString("- Review the proposed subtasks\n")
+	content.WriteString("- Approve or request changes\n")
+	content.WriteString("- When merged, the subtasks will be automatically created and assigned\n")
+
+	return content.String()
+}
+
+// buildTaskPrompt creates a detailed prompt for the LLM
+func buildTaskPrompt(task *tm.Task) string {
+	return fmt.Sprintf(`Task: %s
+
+Priority: %s
+Description: %s
+
+Please provide a complete solution for this task. Include:
+1. Detailed implementation plan
+2. Code changes needed (if applicable)
+3. Files to be created or modified
+4. Testing considerations
+5. Any dependencies or prerequisites
+
+Your response should be comprehensive and actionable.`,
+		task.Title,
+		task.Priority,
+		task.Description)
+}
diff --git a/server/agent/types.go b/server/agent/types.go
deleted file mode 100644
index a9f021c..0000000
--- a/server/agent/types.go
+++ /dev/null
@@ -1,53 +0,0 @@
-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"`
-}