Code Modules in This Guide
Core/Agents
ReactAgent.cs ParallelReactAgent.cs PlanningAgent.cs AgentStep.cs AgentOptions.cs
Core/LLM
ILlmService.cs ClaudeService.cs
Web
Program.cs AgentController.cs
Web/Services
RealMcpClientService.cs

1 What Is an "Agent"?

By itself, Claude just turns text into more text (Guide 01). An agent is a loop around that. It calls Claude, reads what Claude says, optionally calls a tool via the MCP server, feeds the result back to Claude, and repeats — until Claude decides it has a final answer.

C# analogy: Think of an agent as a while loop with an LLM inside. Each iteration: send all context to Claude, get a reply, parse it, invoke any tool it asked for via IMcpServerService.InvokeAsync(), add the result to context, then loop again. The loop exits when Claude's reply contains <final_answer> or when MaxIterations is reached.
Think
Call Tool
Read Result
Think Again
Final Answer

This is the ReAct pattern: Reasoning + Acting. The model reasons about what to do, acts by calling a tool, then reasons again based on the result.

What changed in the MCP Version In the ClaudeLiveKey version, tools were called via a simulated McpServerService — a dictionary of IMcpTool objects running in-process. In this MCP Version, ReactAgent calls the same IMcpServerService interface, but the implementation is RealMcpClientService — which spawns NexaFlowDemo.McpServer as a subprocess and communicates via JSON-RPC 2.0 over stdio. The agent loop code is identical; only the tool dispatch layer changed.

Concrete trace — "What is the stock level for NF-SKU-001 and who supplies it?"

IterationInput sent to ClaudeClaude's replyCode's response
1 System prompt + history: [user] What is the stock level for NF-SKU-001 and who supplies it? <thinking>I need inventory first.</thinking>
<tool_call><name>inventory_lookup</name><input>{"sku":"NF-SKU-001"}</input></tool_call>
Finds <tool_call> → calls RealMcpClientService.InvokeAsync("inventory_lookup", ...) → MCP server subprocess responds → gets result. Adds to history. continue.
2 Same system prompt + history now has 3 entries <thinking>Stock is fine. Now supplier.</thinking>
<tool_call><name>supplier_lookup</name><input>{"id":"SUP-001"}</input></tool_call>
Finds <tool_call> → calls MCP server again → gets supplier data. Adds to history. continue.
3 Everything above + 2 more entries. History now has 5 entries. <thinking>I have both answers.</thinking>
<final_answer>NF-SKU-001: 250 units in stock. Supplier: Pacific Rim Textiles.</final_answer>
Finds <final_answer>yield return new AgentStep { Type = FinalAnswer, ... }yield break. Loop ends.
Notice the growing payload — each iteration sends more tokens This is not a bug — it is how LLMs work. Claude is stateless, so the entire conversation must be re-sent each time.

2 ReactAgent — The Basic Loop

src/NexaFlowDemo.Core/Agents/ReactAgent.cs
for (int i = 0; i < _options.MaxIterations; i++)
{
    // 1. Build prompt from system instructions + full conversation history
    var prompt = BuildPrompt(request, history);

    // 2. Call Claude — get back a string
    string reply = await _llm.CompleteAsync(prompt, ct);
    history.Add(new AgentMessage { Role = "assistant", Content = reply });

    // 3. Did Claude say it has a final answer?
    var finalAnswer = ExtractTag(reply, "final_answer");
    if (!string.IsNullOrWhiteSpace(finalAnswer))
    {
        yield return new AgentStep { Type = AgentStepType.FinalAnswer, Content = finalAnswer };
        yield break;
    }

    // 4. Did Claude ask to call a tool?
    var toolCallXml = ExtractTag(reply, "tool_call");
    if (!string.IsNullOrWhiteSpace(toolCallXml))
    {
        var toolName = ExtractTag(toolCallXml, "name");
        // _mcp is IMcpServerService — in this project: RealMcpClientService
        var toolResult = await _mcp.InvokeAsync(toolName, ParseToolInput(inputJson));

        history.Add(new AgentMessage
        {
            Role    = "tool",
            Content = $"<tool_result><name>{toolName}</name><output>{resultText}</output></tool_result>"
        });
        continue;
    }
}

IAsyncEnumerable — streaming steps in real time

RunAsync returns IAsyncEnumerable<AgentStep>. This lets the SignalR hub push each step to the browser as it arrives — so users see "Thinking...", "Calling inventory_lookup", "Got result: 250 units", "Final answer" appearing live, not all at once after 10 seconds.

C# analogy: It is exactly like IEnumerable<T> with yield return, but async. Each yield return pushes one step to the caller immediately, without waiting for the rest of the loop to finish.

3 ParallelReactAgent — Multiple Tools at Once

src/NexaFlowDemo.Core/Agents/ParallelReactAgent.cs

When Claude needs two or more independent pieces of information, ParallelReactAgent lets it request multiple tools in a single <tool_calls> (plural) block, then runs them all concurrently with Task.WhenAll.

var tasks   = calls.Select(c => _mcp.InvokeAsync(c.Name, c.Input)).ToList();
var results = await Task.WhenAll(tasks);  // all MCP tool calls run in parallel

If each tool call takes 200ms, two sequential calls cost 400ms. With Task.WhenAll, the cost is still ~200ms.

4 PlanningAgent — Plan First, Then Execute

src/NexaFlowDemo.Core/Agents/PlanningAgent.cs

PlanningAgent forces Claude to produce a full plan first, then execute each step in order. It works in three phases:

PhaseWhat HappensLLM calls
1 — PlanAsk Claude to break the task into 2–5 numbered steps in <plan><step>…</step></plan>1
2 — ExecuteFor each step, ask Claude what tool to call. Run the tool via MCP. Collect results.1 per step
3 — SynthesizeAsk Claude to combine all step results into a final coherent answer.1

5 MultiAgentOrchestrator — Routing to the Right Agent

src/NexaFlowDemo.Core/Agents/MultiAgentOrchestrator.cs

From the web layer's perspective, there is only ever one thing to call: IMultiAgentOrchestrator.RunAsync(request). The orchestrator is a router: one entry point, multiple strategies behind it. It picks ReactAgent, ParallelReactAgent, or PlanningAgent based on AgentRunRequest.AgentType.

6 AgentStep — What the Agent Emits

src/NexaFlowDemo.Core/Models/AgentModels.cs
public class AgentStep
{
    public AgentStepType Type       { get; set; }  // Thinking | ToolCall | ToolResult | FinalAnswer | Error
    public string        Content    { get; set; }
    public string?       ToolName   { get; set; }
    public string?       ToolInput  { get; set; }
    public string?       ToolOutput { get; set; }
    public bool          IsError    { get; set; }
}

7 Summary