Events
Every agent, admin, and tool call in LogicGrid publishes a strongly
typed event. The bus is the foundation that the logger and the trace
both subscribe to — but it is also the way you wire your own
handlers: forward to your log/metrics system, react to a
ToolCallFailedEvent, surface a MaxLoopsReachedEvent to operators,
flag ReflexionExhaustedEvent results for human review, and so on.
IAgentEventBus
public interface IAgentEventBus
{
void Subscribe<T>(Action<T> handler) where T : class;
void SubscribeAsync<T>(Func<T, Task> handler) where T : class;
Task PublishAsync<T>(T evt) where T : class;
}
public sealed class AgentEventBus : IAgentEventBus { }
Two subscription modes — sync and async — and one publish method. Handler exceptions are swallowed silently so a bad subscriber can't crash a run.
Get a bus
You almost never need to construct one yourself.
.WithLogging() and
.WithTracing() on AgentContext create one for you.
If you do want to subscribe directly:
var ctx = new AgentContext().WithLogging();
var bus = ctx.EventBus!;
bus.Subscribe<AgentCompletedEvent>(e =>
Console.WriteLine($"{e.AgentName} → {e.TokensUsed.TotalTokens} tokens"));
Catalog
Events served in LogicGrid.Core.Events. Grouped by what they
report on:
Run lifecycle
| Event | Fired when |
|---|---|
RunStartedEvent | An admin's RunAsync is called. |
RunCompletedEvent | An admin's RunAsync returns successfully. Includes total counts and duration. |
RunFailedEvent | An admin's RunAsync throws. |
Agents
| Event | Fired when |
|---|---|
AgentStartedEvent | An agent's RunAsync is invoked (by an admin or directly). |
AgentCompletedEvent | The agent returns. Carries Output, Duration, RetryCount, TokensUsed, Cost. |
AgentFailedEvent | The agent throws. |
LLM calls
| Event | Fired when |
|---|---|
LlmCallStartedEvent | An agent issues an LLM request. Carries the messages array and model name. |
LlmCallCompletedEvent | The LLM responds. Carries response text, TokenUsage, duration. |
LlmCallRetryingEvent | The agent's RetryPolicy triggered a retry. Carries the validation error. |
Tool calls
| Event | Fired when |
|---|---|
ToolCallStartedEvent | An LLM-initiated tool call begins. Carries tool name and argsJson. |
ToolCallCompletedEvent | The tool returns. Carries the result and duration. |
ToolCallFailedEvent | The tool throws. |
GroupChatAdmin
| Event | Fired when |
|---|---|
NextAgentSelectingEvent | The admin asks its LLM to pick the next speaker. Carries the candidate list. |
NextAgentSelectedEvent | The selection is made. Carries the chosen agent's name and the loop number. |
MaxLoopsReachedEvent | The loop hit AdminOptions.MaxLoops without DONE or the configured finalAgentName being chosen. Carries MaxLoops and ForcedFinalAgentName. When the latter is non-null, the admin then invokes that agent once after the loop so the run still produces a usable final result — see Group chat → MaxLoops behaviour. |
ParallelAdmin / MapReduceAdmin
| Event | Fired when |
|---|---|
ParallelRunStartedEvent | Fan-out begins. Carries the agent names being launched. |
ParallelRunCompletedEvent | All branches have completed. Carries the agent count and total duration. |
MapIterationCompletedEvent | A single map item completed. Carries index, total, and output. |
ReflexionAdmin
| Event | Fired when |
|---|---|
ReflexionIterationEvent | Each actor → critic round completes. Carries Iteration, Approved, CriticFeedback. |
ReflexionExhaustedEvent | The loop reached ReflexionOptions.MaxIterations without the critic ever approving. Carries the last actor output and last critic feedback. The admin still returns the last output — subscribe to flag it for human review or downgrade downstream confidence. |
GraphAdmin
| Event | Fired when |
|---|---|
GraphEdgeTraversedEvent | The admin moves from one node to the next. Carries FromNode, ToNode, LoopNumber. |
GraphTerminatedEvent | The graph reaches a terminal node or MaxLoops. Carries the reason. |
Budget
| Event | Fired when |
|---|---|
BudgetWarningEvent | Spend reaches BudgetWarningThreshold of MaxBudgetUsd. Default 80%. |
BudgetExceededEvent | Spend exceeds MaxBudgetUsd. Followed by BudgetExceededException from the admin. |
Subscribing examples
Forward to your own log system
var bus = ctx.EventBus!;
bus.Subscribe<AgentCompletedEvent>(e =>
myLogger.LogInformation(
"Agent {Name} ran in {Ms}ms ({Tokens} tokens)",
e.AgentName, e.Duration.TotalMilliseconds, e.TokensUsed.TotalTokens));
Detect retry storms
bus.Subscribe<LlmCallRetryingEvent>(e =>
{
if (e.AttemptNumber >= 3)
myAlerts.Page($"agent {e.AgentName} hit retry {e.AttemptNumber}");
});
Stream live progress to a UI
bus.SubscribeAsync<AgentStartedEvent>(async e =>
{
await uiHub.SendAsync("step", new { agent = e.AgentName, phase = "start" });
});
bus.SubscribeAsync<AgentCompletedEvent>(async e =>
{
await uiHub.SendAsync("step", new {
agent = e.AgentName, phase = "done", output = e.Output,
});
});
Notes
- Subscriptions are not ordered. If you have two subscribers for the same event, don't depend on which runs first.
- Handler exceptions are swallowed. A buggy subscriber can't crash a run — but you also won't see the exception. Add try/catch in your handler if you want to surface it.
- Subscribe on a fresh bus per run if your handler holds
per-run state. Or use
RunStartedEventto reset it.