Skip to main content

Group chat admin

GroupChatAdmin<TInput, TOutput> runs a dynamic conversation. The admin's LLM picks the next speaker every turn until the work is done.

The conversation ends when:

  1. The admin's LLM decides the task is complete (it's asked every turn whether the work is finished, based on the conversation so far), or
  2. The admin selects an agent whose name equals the finalAgentName constructor argument (its output becomes the final result), or
  3. The loop hits AdminOptions.MaxLoops (default 10).

Example

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

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

IAgent researcher = new Agent<string>(
name: "Researcher",
description: "Gathers facts on a topic.",
systemPrompt: "You research the topic and list 3 key facts.",
llm: llm);

IAgent writer = new Agent<string>(
name: "Writer",
description: "Writes a short article.",
systemPrompt: "You write a 200-word explainer using the researcher's facts.",
llm: llm);

IAgent critic = new Agent<string>(
name: "Critic",
description: "Reviews the article.",
systemPrompt: "Review the article. Reply DONE when publishable, otherwise list fixes.",
llm: llm);

IAgent finaliser = new Agent<string>(
name: "Finaliser",
description: "Produces the publish-ready article.",
systemPrompt: "Return the final, polished article based on the conversation.",
llm: llm);

var admin = new GroupChatAdmin<string, string>(
name: "Editorial",
llmClient: llm,
agents: new[] { researcher, writer, critic, finaliser },
finalAgentName: "Finaliser");

var ctx = new AgentContext().WithLogging();
var article = await admin.RunAsync(
"Explain Reciprocal Rank Fusion in 200 words.");

Console.WriteLine($"\n{article}");
09:14:02.118 [INF] [a3f2c891] Run started — admin: Editorial | task: Explain Reciprocal Rank Fusion in 200 words.
09:14:02.420 [INF] [a3f2c891] Loop 1 — selected agent: Researcher
09:14:05.844 [INF] [a3f2c891] [Researcher] completed | output: 1) RRF combines ranked lists … | 3424ms | 312 tokens
09:14:06.180 [INF] [a3f2c891] Loop 2 — selected agent: Writer
09:14:11.402 [INF] [a3f2c891] [Writer] completed | output: Reciprocal Rank Fusion (RRF) is a way to … | 5222ms | 488 tokens
09:14:11.624 [INF] [a3f2c891] Loop 3 — selected agent: Critic
09:14:13.901 [INF] [a3f2c891] [Critic] completed | output: DONE | 2277ms | 96 tokens
09:14:13.910 [INF] [a3f2c891] Run completed — 3 agents, 4 LLM calls | 11792ms

Constructor

public GroupChatAdmin(
string name,
LlmClientBase llmClient,
IList<IAgent> agents,
string? finalAgentName = null,
AdminOptions? options = null,
IAgentEventBus? eventBus = null)

finalAgentName is a group-chat-only concept and lives on the constructor. It identifies the agent whose turn ends the run (its output becomes the final result).

What happens when MaxLoops is reached

MaxLoops caps the number of agent-selection rounds. When the loop reaches the cap without the LLM emitting DONE or selecting the configured finalAgentName, the admin:

  1. Publishes a MaxLoopsReachedEvent on the event bus. Subscribe to this event to log it, trigger compensating logic, or notify operators.
  2. If finalAgentName is configured and that agent has not run yet, Group chat admin invokes the final agent once after the loop, using the accumulated conversation as its input. Its output becomes the run's result.
  3. If finalAgentName is not configured, the run returns the most recent agent's output.

The behaviour in (2) is intended for pipelines where another step consumes this admin's output. Without it, a stalled run could return an incomplete intermediate response and confuse subsequent logic.

If you prefer the run to surface the MaxLoops instead of producing a result, leave finalAgentName unset and handle the MaxLoopsReachedEvent in your subscriber to interrupt the calling code (for example, by throwing or by setting a flag the caller checks).

Tuning the loop with AdminOptions

var admin = new GroupChatAdmin<string, string>(
name: "Editorial",
llmClient: llm,
agents: new[] { researcher, writer, critic, finaliser },
finalAgentName: "Finaliser", // Finaliser's output is the final answer
options: new AdminOptions
{
MaxLoops = 8,
MaxBudgetUsd = 0.50m, // stop if spend > $0.50
BudgetWarningThreshold = 0.75f,
});

Use it when

  • The order of speakers should depend on the conversation, not be fixed up front.
  • You're modelling a debate, a critique loop, or a panel.
  • Termination is content-dependent — the agents themselves decide when they're done.

Don't use it when

  • The flow is linear → Sequential.
  • You can express the control flow as a graph → Graph.
  • You need a strict turn order. Group chat lets the LLM revisit any agent at any time and may also leave some agents unused for a given run.

Common pitfalls

  • Worker agents don't end the run themselves. The decision to stop is made by the admin's selection LLM, which reads the conversation history each turn. If your agents never produce a clear completion signal in the conversation ("publishable", "all checks passed", etc.), the admin won't recognise the task as done and the loop will run to MaxLoops. Make at least one agent's prompt produce an unambiguous "we're finished" line — or set finalAgentName so a specific agent's turn always ends the run.
  • Ambiguous agent names or descriptions. The admin LLM picks the next speaker from this information. If two agents have overlapping descriptions, selection becomes unstable. Give each agent a specific, non-overlapping description.
  • No budget cap. A misbehaving selection loop can run to MaxLoops on every call. Set AdminOptions.MaxBudgetUsd to bound the spend per run.