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:
- 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
- The admin selects an agent whose name equals the
finalAgentNameconstructor argument (its output becomes the final result), or - The loop hits
AdminOptions.MaxLoops(default10).
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:
- Publishes a
MaxLoopsReachedEventon the event bus. Subscribe to this event to log it, trigger compensating logic, or notify operators. - If
finalAgentNameis 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. - If
finalAgentNameis 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 setfinalAgentNameso 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
MaxLoopson every call. SetAdminOptions.MaxBudgetUsdto bound the spend per run.