Skip to main content

Agents

An agent is an LLM-powered worker defined by a name, description, system prompt, and LLM client. Agents are the fundamental building blocks of LogicGrid; all higher-level structures — including admins, graphs, and RAG — are composed of them.

Building an agent

using LogicGrid.Core.Agents;
using LogicGrid.Core.Llm;

var llm = LlmClientBase.Ollama("llama3.2");

IAgent translator = new Agent<string>(
name: "Translator",
description: "Translates English to French.",
systemPrompt:
"Translate the user's English text to natural, fluent French. " +
"Output only the translation — no commentary.",
llm: llm);

var ctx = new AgentContext();
Console.WriteLine(
await translator.RunAsync("Hello, how are you?", ctx));
Bonjour, comment ça va ?

Constructor reference

public Agent(
string name,
string description,
string systemPrompt,
LlmClientBase llm,
IList<ToolBase>? tools = null,
LlmOptions? llmOptions = null,
RetryPolicy? retryPolicy = null,
IToolCallingStrategy? toolCallingStrategy = null)
ParameterRequiredWhat for
nameyesShort identifier. Admins use it for selection; the logger prefixes lines with it.
descriptionyesOne-line role description. Specificity helps the Admin LLM select the correct agent for the task.
systemPromptyesInstructions sent with every call. Supports {{slot}} injection for dynamic templates — see Prompt templates.
llmyesThe LLM client. Different agents in the same app can use different providers — see Providers.
toolsoptionalTools available to the agent. See Tools.
llmOptionsoptionalGeneration options (Temperature, MaxTokens, etc.). See Providers.
retryPolicyoptionalControls retries on validation failure. See Retry policies.
toolCallingStrategyoptionalDetermines the tool invocation protocol. defaults to PromptSchemaStrategy for universal compatibility or use NativeToolCallingStrategy for providers with built-in tool support. See Tool calling strategy.

Agent<T> and AgentBase<T>

LogicGrid ships two agent classes. Both live in LogicGrid.Core.Agents.

TypeWhat it is
Agent<TOutput>Concrete class. Configure name, description, system prompt, LLM, and tools through the constructor.
AgentBase<TOutput>Abstract class. Use it to customise prompt rendering, output validation, deserialization, retry behaviour or how agent runs. For more details on how to customize AgentBase<T> see Overriding AgentBase<T>

Return type & typed output

Agent<TOutput> provides a dual-interface for execution. While the concrete class returns a strongly-typed object, its explicit IAgent implementation provides a serialized string. This design allows the same agent instance to serve both local business logic and framework-level orchestration.

  • Typed Execution (Agent<TOutput>): Calling RunAsync on the concrete generic type returns TOutput. LogicGrid automatically handles the heavy lifting: it extracts JSON from the LLM's response, deserializes it into your type, and executes a retry loop if the output fails validation or schema constraints.
public sealed class Sentiment
{
public string Label { get; set; } = ""; // positive / negative / neutral
public double Confidence { get; set; }
}

var sentiment = new Agent<Sentiment>(
name: "Sentiment",
description: "Classifies sentiment.",
systemPrompt:
"Classify the sentiment of the user's text. " +
"Reply with JSON: { \"label\": \"...\", \"confidence\": 0.0 }",
llm: llm);

Sentiment s = await sentiment.RunAsync(
"I love this library.", new AgentContext());

Console.WriteLine($"{s.Label} ({s.Confidence:P0})");
positive (95%)
  • Erased Execution (IAgent): By implementing IAgent.RunAsync explicitly, LogicGrid avoids method signature collisions while providing a string-based contract. When called through this interface, the agent serializes its TOutput to a JSON string. This "type erasure" is what enables heterogeneous agents to be composed into pipelines or graphs that expect a uniform Task<string> exchange.
public sealed class Sentiment
{
public string Label { get; set; } = ""; // positive / negative / neutral
public double Confidence { get; set; }
}

IAgent sentiment = new Agent<Sentiment>(
name: "Sentiment",
description: "Classifies sentiment.",
systemPrompt:
"Classify the sentiment of the user's text. " +
"Reply with JSON: { \"label\": \"...\", \"confidence\": 0.0 }",
llm: llm);

Sentiment s = await sentiment.RunAsync(
"I love this library.", new AgentContext());

Console.WriteLine($"{s.Label} ({s.Confidence:P0})");
{"Label":"positive","Confidence":0.95}

Tools

Tools are passed to the constructor. The agent's LLM decides when to call them; LogicGrid handles the call-loop and feeds results back.

using LogicGrid.Core.Tools;
using LogicGrid.Tools.Tools;

IAgent math = new Agent<string>(
name: "Mathlete",
description: "Solves arithmetic.",
systemPrompt: "Use the calculator tool when the user asks for a number.",
llm: llm,
tools: new ToolBase[] { new CalculatorTool() });

Console.WriteLine(
await math.RunAsync("What is 17 * 23 + 91?", new AgentContext()));
17 * 23 + 91 = 482.

See Built-in tools, Custom tools (typed), and MCP.

Scoped message history

The agent's message history is reset on every RunAsync call. There is no context bleed between invocations. This is intentional:

  • Agents become predictable — the same input gives the same prompt shape every time.
  • They become safe to share between threads and concurrent pipelines.
  • They become testable without setup teardown.

If you need cross-call memory, you have two options:

Events

When an AgentContext carries an EventBus, the agent publishes a strongly typed event at every step of a run: AgentStartedEvent, LlmCallStartedEvent, LlmCallCompletedEvent, LlmCallRetryingEvent, ToolCallStartedEvent, ToolCallCompletedEvent, ToolCallFailedEvent, AgentCompletedEvent, AgentFailedEvent. Subscribe your own handler to forward them to your log/metrics system, react to failures. The full catalog is on the Events page.

Customising via AgentBase<T>

AgentBase<T> exposes virtual members you can override:

  • RenderSystemPromptAsync — build the prompt asynchronously (RAG retrieval, user-profile lookup, etc.).
  • ValidateOutput — reject responses that don't pass your rule so the framework retries automatically.
  • DeserializeOutput — parse a non-JSON response format.
  • OnRetry — emit your own telemetry on retries.
  • Computed Tools / LlmOptions / RetryPolicy — when those depend on state that isn't available at construction time.

The full walkthrough is at Overriding AgentBase<T>.