diff --git a/EmailSearch/Agents/AIAgentRunner.cs b/EmailSearch/Agents/AIAgentRunner.cs
deleted file mode 100644
index 7d21f06..0000000
--- a/EmailSearch/Agents/AIAgentRunner.cs
+++ /dev/null
@@ -1,100 +0,0 @@
-using System.Text.Json;
-
-namespace EmailSearch.Agents;
-
-///
-/// Entry point for running the AI Email Agent standalone.
-///
-public static class AIAgentRunner
-{
- public static async Task RunAsync(CancellationToken cancellationToken = default)
- {
- Console.WriteLine("╔═══════════════════════════════════════════════════════════╗");
- Console.WriteLine("║ AI EMAIL AGENT - Powered by Claude ║");
- Console.WriteLine("╚═══════════════════════════════════════════════════════════╝");
- Console.WriteLine();
-
- var config = LoadConfig();
-
- if (string.IsNullOrEmpty(config.AnthropicApiKey))
- {
- Console.WriteLine("ERROR: Anthropic API key not configured!");
- Console.WriteLine();
- Console.WriteLine("Set your API key via:");
- Console.WriteLine(" 1. Environment variable: ANTHROPIC_API_KEY");
- Console.WriteLine(" 2. Config file: AIAgentConfig.json");
- Console.WriteLine();
- return;
- }
-
- Console.WriteLine($"Model: {config.Model}");
- Console.WriteLine($"Watching: {config.WatchFolder}");
- Console.WriteLine($"Gotify: {(string.IsNullOrEmpty(config.GotifyUrl) ? "(not configured)" : config.GotifyUrl)}");
- Console.WriteLine();
- Console.WriteLine("User priorities:");
- foreach (var p in config.UserPriorities)
- Console.WriteLine($" • {p}");
- Console.WriteLine();
- Console.WriteLine("Press Ctrl+C to stop...");
- Console.WriteLine();
-
- using var agent = new AIEmailAgent(config);
- await agent.StartAsync(cancellationToken);
-
- try
- {
- await Task.Delay(Timeout.Infinite, cancellationToken);
- }
- catch (OperationCanceledException) { }
-
- await agent.StopAsync(CancellationToken.None);
- }
-
- private static AIAgentConfig LoadConfig()
- {
- var configPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "AIAgentConfig.json");
-
- AIAgentConfig? config = null;
-
- if (File.Exists(configPath))
- {
- try
- {
- var json = File.ReadAllText(configPath);
- config = JsonSerializer.Deserialize(json, new JsonSerializerOptions
- {
- PropertyNameCaseInsensitive = true
- });
- }
- catch (Exception ex)
- {
- Console.WriteLine($"Warning: Failed to load config: {ex.Message}");
- }
- }
-
- config ??= new AIAgentConfig();
-
- // Override with environment variables if set
- config.AnthropicApiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY")
- ?? config.AnthropicApiKey;
- config.GotifyUrl = Environment.GetEnvironmentVariable("GOTIFY_URL")
- ?? config.GotifyUrl;
- config.GotifyToken = Environment.GetEnvironmentVariable("GOTIFY_TOKEN")
- ?? config.GotifyToken;
-
- // Default priorities if none configured
- if (config.UserPriorities.Count == 0)
- {
- config.UserPriorities = new List
- {
- "Emails from my boss or direct team members",
- "Urgent or time-sensitive requests",
- "Meeting changes and calendar updates",
- "Security alerts from legitimate services",
- "Customer inquiries that need quick response"
- };
- }
-
- return config;
- }
-}
diff --git a/EmailSearch/Agents/AIEmailAgent.cs b/EmailSearch/Agents/AIEmailAgent.cs
deleted file mode 100644
index 2abccec..0000000
--- a/EmailSearch/Agents/AIEmailAgent.cs
+++ /dev/null
@@ -1,685 +0,0 @@
-using System.Net.Http.Headers;
-using System.Text;
-using System.Text.Json;
-using System.Text.Json.Serialization;
-using Microsoft.Extensions.Hosting;
-using NetOffice.OutlookApi;
-using NetOffice.OutlookApi.Enums;
-using EmailSearch.SpamDetection;
-using OutlookApp = NetOffice.OutlookApi.Application;
-using Exception = System.Exception;
-
-namespace EmailSearch.Agents;
-
-///
-/// A true AI Agent that uses Claude to reason about emails and decide what actions to take.
-/// The LLM is in the decision loop - it evaluates each email and chooses actions dynamically.
-///
-public class AIEmailAgent : BackgroundService
-{
- private readonly AIAgentConfig _config;
- private readonly HttpClient _httpClient;
- private readonly SpamDetector _spamDetector;
- private OutlookApp? _outlookApp;
- private _NameSpace? _namespace;
- private MAPIFolder? _watchFolder;
- private Items? _items;
-
- // Tools the agent can use
- private readonly List _tools;
-
- public AIEmailAgent(AIAgentConfig config)
- {
- _config = config;
- _httpClient = new HttpClient();
- _httpClient.DefaultRequestHeaders.Add("x-api-key", config.AnthropicApiKey);
- _httpClient.DefaultRequestHeaders.Add("anthropic-version", "2023-06-01");
- _spamDetector = new SpamDetector();
-
- // Define tools the agent can use
- _tools = DefineTools();
- }
-
- private List DefineTools()
- {
- return new List
- {
- new AgentTool
- {
- Name = "send_notification",
- Description = "Send a push notification to the user's phone via Gotify. Use for important emails that need immediate attention.",
- InputSchema = new
- {
- type = "object",
- properties = new
- {
- title = new { type = "string", description = "Short notification title" },
- message = new { type = "string", description = "Notification body with key details" },
- priority = new { type = "integer", description = "1-10, where 10 is most urgent. Use 8+ for truly urgent, 5-7 for important, 1-4 for informational" }
- },
- required = new[] { "title", "message", "priority" }
- }
- },
- new AgentTool
- {
- Name = "move_to_folder",
- Description = "Move the email to a specific Outlook folder. Use for organizing emails (e.g., move spam to Junk, receipts to Archive).",
- InputSchema = new
- {
- type = "object",
- properties = new
- {
- folder = new { type = "string", description = "Target folder: Junk, Archive, or any custom folder name" },
- reason = new { type = "string", description = "Brief reason for moving" }
- },
- required = new[] { "folder", "reason" }
- }
- },
- new AgentTool
- {
- Name = "flag_email",
- Description = "Flag the email for follow-up with a category. Use when the email needs future attention but not immediate notification.",
- InputSchema = new
- {
- type = "object",
- properties = new
- {
- category = new { type = "string", description = "Category to apply: 'Follow Up', 'Review', 'Waiting', 'Important'" },
- reason = new { type = "string", description = "Why this needs follow-up" }
- },
- required = new[] { "category" }
- }
- },
- new AgentTool
- {
- Name = "log_observation",
- Description = "Log an observation about the email without taking action. Use when the email is noted but requires no action.",
- InputSchema = new
- {
- type = "object",
- properties = new
- {
- observation = new { type = "string", description = "What you observed about this email" }
- },
- required = new[] { "observation" }
- }
- },
- new AgentTool
- {
- Name = "analyze_spam",
- Description = "Run detailed spam analysis on the email to get risk score and red flags. Use when you're unsure if an email is legitimate.",
- InputSchema = new
- {
- type = "object",
- properties = new
- {
- include_details = new { type = "boolean", description = "Include detailed feature breakdown" }
- },
- required = new string[] { }
- }
- }
- };
- }
-
- protected override async Task ExecuteAsync(CancellationToken stoppingToken)
- {
- Log("AI Email Agent starting...");
- Log($"Model: {_config.Model}");
- Log($"Watching: {_config.WatchFolder}");
-
- try
- {
- InitializeOutlook();
- Log("Connected to Outlook. AI Agent ready and watching for emails...");
- Log("");
-
- while (!stoppingToken.IsCancellationRequested)
- {
- await Task.Delay(TimeSpan.FromSeconds(30), stoppingToken);
- }
- }
- catch (Exception ex)
- {
- Log($"Error: {ex.Message}");
- }
- }
-
- private void InitializeOutlook()
- {
- _outlookApp = new OutlookApp();
- _namespace = _outlookApp.GetNamespace("MAPI");
-
- _watchFolder = _config.WatchFolder.Equals("Inbox", StringComparison.OrdinalIgnoreCase)
- ? _namespace.GetDefaultFolder(OlDefaultFolders.olFolderInbox)
- : FindFolder(_namespace, _config.WatchFolder);
-
- if (_watchFolder == null)
- throw new Exception($"Folder '{_config.WatchFolder}' not found");
-
- _items = (Items)_watchFolder.Items;
- _items.ItemAddEvent += OnNewEmailReceived;
- }
-
- private void OnNewEmailReceived(object item)
- {
- if (item is not MailItem mail)
- return;
-
- // Run agent processing on a background thread
- _ = Task.Run(async () =>
- {
- try
- {
- await ProcessEmailWithAgent(mail);
- }
- catch (Exception ex)
- {
- Log($"Agent error: {ex.Message}");
- }
- });
- }
-
- ///
- /// The core agentic loop - sends email to Claude, executes tool calls, continues until done.
- ///
- private async Task ProcessEmailWithAgent(MailItem mail)
- {
- var subject = mail.Subject ?? "(No Subject)";
- var sender = mail.SenderName ?? mail.SenderEmailAddress ?? "Unknown";
- var senderEmail = mail.SenderEmailAddress ?? "";
- var body = TruncateBody(mail.Body ?? "", 2000);
- var receivedTime = mail.ReceivedTime;
-
- Log($"");
- Log($"═══════════════════════════════════════════════════════════");
- Log($"NEW EMAIL: {subject}");
- Log($"From: {sender} <{senderEmail}>");
- Log($"Time: {receivedTime}");
- Log($"═══════════════════════════════════════════════════════════");
-
- // Build context for the agent
- var systemPrompt = BuildSystemPrompt();
- var userMessage = BuildEmailContext(mail, subject, sender, senderEmail, body);
-
- var messages = new List
- {
- new AgentMessage { Role = "user", Content = userMessage }
- };
-
- // Agentic loop - keep going until Claude stops calling tools
- var iteration = 0;
- const int maxIterations = 5;
-
- while (iteration < maxIterations)
- {
- iteration++;
- Log($"");
- Log($"[Agent thinking... iteration {iteration}]");
-
- var response = await CallClaudeAsync(systemPrompt, messages);
-
- if (response == null)
- {
- Log("[Agent] Failed to get response from Claude");
- break;
- }
-
- // Process the response
- var hasToolUse = false;
- var toolResults = new List();
-
- foreach (var content in response.Content)
- {
- if (content.Type == "text" && !string.IsNullOrWhiteSpace(content.Text))
- {
- Log($"[Agent] {content.Text}");
- }
- else if (content.Type == "tool_use" && content.Input.HasValue)
- {
- hasToolUse = true;
- Log($"[Agent calling tool: {content.Name}]");
-
- // Execute the tool
- var result = await ExecuteToolAsync(content.Name!, content.Input.Value, mail);
- toolResults.Add(new ToolResultContent
- {
- Type = "tool_result",
- ToolUseId = content.Id!,
- Content = result
- });
- }
- }
-
- // If no tool calls, agent is done
- if (!hasToolUse)
- {
- Log($"[Agent complete]");
- break;
- }
-
- // Add assistant response and tool results to continue the conversation
- messages.Add(new AgentMessage
- {
- Role = "assistant",
- Content = response.Content
- });
-
- messages.Add(new AgentMessage
- {
- Role = "user",
- Content = toolResults.Cast