Monitoring
FabrCore 2.0 · Release and package availability
These guides track the current 2.0 source. Stable 2.0.0 publication is pending; package commands show the release target. Until it is published, follow the source quick start or use a matching available prerelease set. Release migration · Runtime modes
Agent Monitoring
FabrCore provides a pluggable monitoring system for observing message traffic, event streams, and internal LLM calls across your agent system. Messages, events, and LLM calls live in separate buffers with independent query methods and notification events.
IAgentMessageMonitor
The IAgentMessageMonitor interface is the core abstraction for agent monitoring. FabrCore ships with three implementations:
| Implementation | Description |
|---|---|
InMemoryAgentMessageMonitor | Opt-in bounded FIFO buffer for messages, events, and LLM calls |
SqlAgentMessageMonitor | Opt-in buffered SQL monitoring through FabrCore:Monitoring:Provider=sql; requires the operational schema |
NullAgentMessageMonitor | No-op implementation registered when monitoring is not enabled |
Enabling Monitoring
Monitoring is opt-in. Enable it in AddFabrCoreServer:
using FabrCore.Host;
// Call once during host setup. Metadata-only capture is the default.
builder.AddFabrCoreServer(new FabrCoreServerOptions()
.UseInMemoryAgentMessageMonitor());
CapturePayloads = true, the monitor stores actual prompts and responses sent to the LLM. These can contain PII or secrets. Always configure Redact and ensure your storage is trusted. The default is metadata-only.
To use a custom implementation:
// MyAgentMessageMonitor is your implementation of IAgentMessageMonitor.
builder.AddFabrCoreServer(new FabrCoreServerOptions()
.UseAgentMessageMonitor<MyAgentMessageMonitor>());
Monitored Types
MonitoredMessage
Captures every message an agent receives (inbound) and every response it sends (outbound).
| Property | Type | Description |
|---|---|---|
Id | string | Unique message identifier |
AgentHandle | string | Fully-qualified agent handle |
Direction | MessageDirection | Inbound or Outbound |
Timestamp | DateTimeOffset | When the message was recorded |
Body | string? | Message body text |
LlmUsage | LlmUsageInfo? | Token usage for outbound responses |
MonitoredEvent
Captures events that reach an agent's OnEvent handler (fire-and-forget).
| Property | Type | Description |
|---|---|---|
Id | string | Event identifier |
AgentHandle | string | Agent that received the event |
Type | string | CloudEvents-compatible event type |
Subject | string? | Event subject |
Timestamp | DateTimeOffset | When the event was recorded |
MonitoredLlmCall
Captures calls made through FabrCore's tracked chat clients. Independently constructed clients need their own instrumentation.
| Property | Type | Description |
|---|---|---|
AgentHandle | string | Agent that made the call |
Model | string? | Model used for the call |
InputTokens | int | Prompt tokens consumed |
OutputTokens | int | Completion tokens generated |
DurationMs | long | Call duration in milliseconds |
Streaming | bool | Whether the call used streaming |
OriginContext | string | Where the call originated (see below) |
ParentMessageId | string? | Linked message ID when called from OnMessage |
Timestamp | DateTimeOffset | When the call was recorded |
RequestMessages | List? | Prompt payloads (when CapturePayloads enabled) |
OriginContext values:
| Value | Description |
|---|---|
OnMessage:<id> | Inside a normal OnMessage flow |
OnEvent:<type> | Inside an OnEvent handler |
Timer:<name> | Inside a timer tick |
Reminder:<name> | Inside a reminder tick |
Compaction | Inside chat history compaction |
Background | Outside any scope |
Querying Monitored Data
Inject IAgentMessageMonitor and use the three separate query methods:
public class MonitorDashboard
{
private readonly IAgentMessageMonitor _monitor;
public MonitorDashboard(IAgentMessageMonitor monitor)
{
_monitor = monitor;
}
public async Task ShowActivity(string agentHandle)
{
// Messages (most recent first)
var messages = await _monitor.GetMessagesAsync(
agentHandle: agentHandle, limit: 50);
// Events delivered to the agent
var events = await _monitor.GetEventsAsync(
agentHandle: agentHandle, limit: 50);
// LLM calls made by the agent
var llmCalls = await _monitor.GetLlmCallsAsync(
agentHandle: agentHandle, limit: 20);
foreach (var call in llmCalls)
{
Console.WriteLine(
$"[{call.Timestamp:HH:mm:ss}] {call.OriginContext} " +
$"model={call.Model} tokens={call.InputTokens}/{call.OutputTokens} " +
$"dur={call.DurationMs}ms");
}
}
}
Token Tracking
FabrCore tracks LLM token usage at two levels:
Per-Call Tracking
Every LLM call captured by the monitor includes InputTokens and OutputTokens. The TokenTrackingChatClient wraps any IChatClient to automatically record each call to the monitor.
Per-Agent Totals
The AgentTokenSummary provides accumulated totals per agent:
var summary = await _monitor.GetAgentTokenSummaryAsync("user1:my-agent");
if (summary is not null)
{
Console.WriteLine($"Total input tokens: {summary.TotalInputTokens}");
Console.WriteLine($"Total output tokens: {summary.TotalOutputTokens}");
Console.WriteLine($"Total calls: {summary.TotalLlmCalls}");
}
LlmUsageScope
LlmUsageScope is an AsyncLocal scope automatically set by OnMessage, carrying the agent handle, parent message ID, trace ID, and origin. This ensures LLM calls made during message processing are correctly attributed.
LlmCallContext
For LLM calls outside of OnMessage (timers, events, background work), use LlmCallContext to tag the origin:
using (LlmCallContext.Set("Timer:cleanup"))
{
// LLM calls here will be attributed to "Timer:cleanup"
var response = await chatClient.GetResponseAsync(messages);
}
Real-Time Notifications
Subscribe to events for real-time updates as messages, events, and LLM calls are recorded:
_monitor.OnMessageRecorded += message =>
{
// Push to UI when a message is captured
};
_monitor.OnEventRecorded += evt =>
{
// Push to UI when an event is captured
};
_monitor.OnLlmCallRecorded += call =>
{
// Push to UI when an LLM call completes
};
Custom Monitor Implementations
Implement IAgentMessageMonitor to build custom monitoring backends (SQL database, external logging service, etc.):
// Implement the complete contract from FabrCore.Core.Monitoring.
// It includes recording, queries, summaries, notifications and ClearAsync.
// Query collections are List<T>; per-agent summaries can be null.
LlmCaptureOptions
| Property | Default | Description |
|---|---|---|
Enabled | true | Master switch for LLM call capture |
CapturePayloads | false | Store actual prompts and responses |
MaxPayloadChars | 8000 | Per-field character cap for payloads |
MaxToolArgsChars | 4000 | Separate cap for tool arguments |
MaxBufferedCalls | 2000 | FIFO buffer size for LLM calls |
Redact | null | Function to redact sensitive content before storage |
Retained cloud queries and optional SQL storage
FabrCore 2.0 adds query interfaces alongside recording providers. Filter by principal, agent, trace, time, channel, kind and execution category before paging (100 default, 1,000 maximum), with byte limits and opaque cursors. Fetch captured payloads separately in bounded chunks. Read cloud monitoring for source identity, gaps, retention and hidden-view polling behavior.
Opt in with FabrCore:Monitoring:Provider=sql and the operational database. Defaults are seven-day retention, 10,000 queued records / 64 MiB, batches up to 250 and one-second flushing. Recording does not await SQL; queue saturation and write failures appear as drops and persistence health. Disabled, in-memory and custom providers remain supported. SQL mode alone does not enable monitoring.
Insights refreshes manually by default, optionally every ten seconds while visible, with at most four concurrent host requests. Shared stores are queried once by opaque identity; silo-local sources are queried separately. Missing capture and offline hosts remain visible. Existing local notifications and SSE do not require a continuous cloud stream.
Implement custom providers against the complete current interface. For durable monitoring, retention, queue limits and outage behavior, see cloud administration.