![AI Code Review Image]()
In Part 1, we built a simple AI-powered code review system using Git diff and an LLM.
That approach works well for basic automation, but modern AI systems are evolving into something much more powerful: AI Agents.
Instead of sending one large prompt to an LLM, AI Agents:
In this article, we’ll build a modular AI Agent in C# capable of:
What Is an AI Agent?
Traditional LLM workflow:
Prompt → LLM → Response
AI Agent workflow:
Task
↓
Planner
↓
Skills / Tools
↓
LLM Reasoning
↓
Actions
↓
Validation
↓
Final Output
This makes the system far more intelligent and extensible.
What Are Skills?
Skills are modular capabilities an AI Agent can execute.
Examples:
| Skill | Purpose |
|---|
| Git Diff Skill | Extract code changes |
| Security Review Skill | Detect vulnerabilities |
| Performance Review Skill | Find inefficient code |
| Fix Generator Skill | Generate fixes |
| Unit Test Skill | Create test cases |
| Validation Skill | Verify generated code |
Instead of one giant prompt, the agent orchestrates multiple focused operations.
Agent Architecture
Developer Request
↓
AI Agent
↓
Skill Orchestrator
┌──────────────┐
│ Git Diff │
│ Code Review │
│ Fix Generator│
│ Validation │
└──────────────┘
↓
LLM
↓
Review + Fix Output
Step 1 — Define a Skill Contract
public interface IAiSkill
{
string Name { get; }
Task<string> ExecuteAsync(AiContext context);
}
Every skill follows a standard contract.
Step 2 — Create Shared Context
public class AiContext
{
public string GitDiff { get; set; }
public List<string> Findings { get; set; }
= new();
}
This shared context acts like agent memory.
Step 3 — Implement Git Diff Skill
public class GitDiffSkill : IAiSkill
{
public string Name => "GitDiff";
public async Task<string> ExecuteAsync(AiContext context)
{
string diff = await GitHelper.GetDiffAsync();
context.GitDiff = diff;
return diff;
}
}
Step 4 — Implement Review Skill
public class CodeReviewSkill : IAiSkill
{
private readonly IChatClient _chatClient;
public string Name => "CodeReview";
public CodeReviewSkill(IChatClient chatClient)
{
_chatClient = chatClient;
}
public async Task<string> ExecuteAsync(AiContext context)
{
string prompt = $@"
Review this C# git diff.
Focus on:
- Bugs
- Security
- Performance
Diff:
{context.GitDiff}
";
string result =
await _chatClient.GetResponseAsync(prompt);
context.Findings.Add(result);
return result;
}
}
Step 5 — Add Fix Generation Skill
public class FixSuggestionSkill : IAiSkill
{
private readonly IChatClient _chatClient;
public string Name => "FixSuggestion";
public FixSuggestionSkill(IChatClient chatClient)
{
_chatClient = chatClient;
}
public async Task<string> ExecuteAsync(AiContext context)
{
string findings =
string.Join("\n", context.Findings);
string prompt = $@"
Generate corrected C# code fixes
for these findings:
{findings}
";
return await _chatClient.GetResponseAsync(prompt);
}
}
Step 6 — Build Agent Orchestrator
public class CodeReviewAgent
{
private readonly List<IAiSkill> _skills;
public CodeReviewAgent(List<IAiSkill> skills)
{
_skills = skills;
}
public async Task RunAsync()
{
var context = new AiContext();
foreach (var skill in _skills)
{
Console.WriteLine(
$"Running: {skill.Name}");
var result =
await skill.ExecuteAsync(context);
Console.WriteLine(result);
}
}
}
Final Thoughts
Part 2 introduced a modular AI Agent architecture.
Instead of:
we now have:
Reusable skills
Shared memory
Orchestrated workflows
Auto-fix generation
However, prompts are still embedded inside C# code.
In Part 3, we’ll externalize intelligence into reusable markdown-based skills.md files and dynamically load them into the agent.