withObservability() turns on distributed tracing, the metrics dashboard, and structured logging with a single builder call. This recipe shows how to use each piece.
Enabling the Dashboard
Section titled βEnabling the Dashboardβimport { ReactiveAgents } from "reactive-agents";
const agent = await ReactiveAgents.create() .withName("research-bot") .withProvider("anthropic") .withReasoning() .withTools() .withObservability({ verbosity: "normal" }) .build();
const result = await agent.run("Summarize the top 5 papers on transformer attention");// Dashboard is printed automatically when the run completesAt verbosity: "normal" you get a dashboard like this printed to stdout:
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
Agent Execution Summary ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ€β Status: β
Success Duration: 13.9s Steps: 7 ββ Tokens: 1,963 Cost: ~$0.003 Model: haiku-4.5 ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
π Execution Timelineββ [bootstrap] 100ms β
ββ [guardrail] 50ms β
ββ [strategy] 50ms β
ββ [think] 10,001ms β οΈ (7 iter, 72% of time)ββ [act] 1,000ms β
(2 tools)ββ [observe] 500ms β
ββ [memory-flush] 200ms β
ββ [complete] 28ms β
π§ Tool Execution (2 called)ββ web-search β
2 calls, 350ms avgββ file-write β
1 call, 120ms avg
β οΈ Alerts & Insightsββ think phase blocked β₯10s (LLM latency)No manual instrumentation is needed. MetricsCollector auto-subscribes to the EventBus and aggregates all phase timings, tool calls, token usage, and cost estimates.
Verbosity Levels
Section titled βVerbosity Levelsβ| Level | Dashboard | Real-time output |
|---|---|---|
"minimal" | Not shown | Start + complete lines only |
"normal" (default) | Full dashboard | Phase transitions + tool names |
"verbose" | Full dashboard | + reasoning steps + LLM call summary |
"debug" | Full dashboard | + full prompt/tool I/O (no truncation) |
Live Phase Streaming
Section titled βLive Phase StreamingβSet live: true to stream phase events to the console as the agent runs, in addition to the end-of-run dashboard:
const agent = await ReactiveAgents.create() .withProvider("anthropic") .withReasoning() .withTools() .withObservability({ verbosity: "verbose", live: true }) .build();
// Output as the agent runs:// β [bootstrap] 0 semantic, 0 episodic | 12ms// β [strategy] reactive | tools: web-search, file-write// β [thought] I need to search for recent transformer papers...// β [action] web-search({"query":"transformer attention 2025"})// β [obs] Found 47 results [1,204 chars]// β [think] 5 steps | 4,800 tok | 8.1s// β [act] web-search (1 tool)// β [complete] β task-abc | 4,800 tok | $0.0002 | 8.3sReading the Debrief
Section titled βReading the DebriefβWhen reasoning is enabled, every run produces a structured AgentDebrief attached to the result:
const result = await agent.run("Compare React and Vue for a large SPA project");
if (result.debrief) { console.log(result.debrief.summary); // "The agent compared React and Vue across performance, ecosystem, and..."
console.log(result.debrief.keyFindings); // ["React has a larger ecosystem", "Vue has gentler learning curve", ...]
console.log(result.debrief.metrics); // { iterations: 4, toolCalls: 2, tokensUsed: 2100 }
console.log(result.terminatedBy); // "final_answer" | "max_iterations" | "error"}The debrief is also persisted to SQLite (agent_debriefs table) if memory is enabled, so you can query historical run data.
Subscribing to EventBus Events
Section titled βSubscribing to EventBus EventsβFor custom monitoring integrations, subscribe to the typed EventBus directly:
import { ReactiveAgents } from "reactive-agents";
const agent = await ReactiveAgents.create() .withProvider("anthropic") .withReasoning() .withTools() .build();
// Subscribe to specific event types (fully typed)agent.subscribe("ToolCallCompleted", (event) => { // event.toolName, event.durationMs, event.success are all typed console.log(`Tool ${event.toolName} took ${event.durationMs}ms`);});
agent.subscribe("ReasoningStepCompleted", (event) => { if (event.thought) console.log(`Thought: ${event.thought}`); if (event.action) console.log(`Action: ${event.action}`); if (event.observation) console.log(`Obs: ${event.observation}`);});
agent.subscribe("FinalAnswerProduced", (event) => { console.log(`Done in ${event.iteration} steps, ${event.totalTokens} tokens`);});
// Or catch-all for all eventsagent.subscribe((event) => { myMonitoringSystem.track(event._tag, event);});
await agent.run("What is the top story on Hacker News right now?");await agent.dispose();Available Event Tags
Section titled βAvailable Event Tagsβ| Tag | When it fires |
|---|---|
AgentStarted | Task begins execution |
AgentCompleted | Task finishes (success or failure) |
ReasoningStepCompleted | Each thought/action/observation step |
ReasoningFailed | Strategy error during reasoning loop |
FinalAnswerProduced | Final answer extracted from loop |
ToolCallCompleted | Each tool call (success or failure) |
GuardrailViolationDetected | Input blocked by guardrails |
LLMRequestStarted | LLM API call begins |
MemoryBootstrapped | Memory loaded at task start |
MemoryFlushed | Memory written at task end |
IterationProgress | Every reasoning loop iteration (streaming) |
StrategySwitched | Strategy switching triggered |
Wiring External Monitoring
Section titled βWiring External MonitoringβSending Metrics to Prometheus / Datadog
Section titled βSending Metrics to Prometheus / Datadogβconst agent = await ReactiveAgents.create() .withProvider("anthropic") .withReasoning() .withTools() .build();
// Collect metrics from eventsagent.subscribe("ToolCallCompleted", (event) => { // Prometheus-style counter toolCallCounter.inc({ tool: event.toolName, success: String(event.success) }); // Histogram for latency toolLatencyHistogram.observe({ tool: event.toolName }, event.durationMs / 1000);});
agent.subscribe("AgentCompleted", (event) => { runDurationGauge.set(event.durationMs ?? 0); tokenUsageCounter.inc(event.tokensUsed ?? 0);});Structured Logging to Files
Section titled βStructured Logging to FilesβUse withLogging() independently of the full observability stack:
const agent = await ReactiveAgents.create() .withProvider("anthropic") .withReasoning() .withLogging({ level: "info", format: "json", output: "file", filePath: "./logs/agent.log", maxFileSizeMb: 50, maxFiles: 7, }) .build();
// All agent events are written as JSON lines to ./logs/agent.log// Automatically rotates at 50 MB, keeps 7 rotated filesEach JSON log entry includes timestamp, level, message, agentId, sessionId, traceId, and any custom metadata.
Health Probes
Section titled βHealth ProbesβwithHealthCheck() adds a agent.health() method that tests every wired service:
const agent = await ReactiveAgents.create() .withProvider("anthropic") .withReasoning() .withMemory() .withGuardrails() .withHealthCheck() .build();
const health = await agent.health();// {// status: "healthy", // "healthy" | "degraded" | "unhealthy"// checks: [// { name: "llm-provider", status: "healthy", latencyMs: 234 },// { name: "memory", status: "healthy", latencyMs: 12 },// { name: "guardrails", status: "healthy", latencyMs: 1 },// ]// }
if (health.status !== "healthy") { console.error("Agent degraded:", health.checks.filter(c => c.status !== "healthy"));}Call agent.health() from a Kubernetes readiness probe, a /health HTTP endpoint, or a pre-run guard in your application code.
Distributed Tracing
Section titled βDistributed TracingβEvery execution produces a trace tree. View it via obs.flush() after a run:
import { ReactiveAgents } from "reactive-agents";import { ObservabilityService } from "@reactive-agents/observability";import { Effect } from "effect";
const agent = await ReactiveAgents.create() .withProvider("anthropic") .withReasoning() .withObservability({ verbosity: "normal" }) .build();
await agent.run("Draft a short blog post about Effect-TS");// Dashboard printed here
// Force-flush any buffered spans to the exporter// (useful when using file or remote exporters)Each trace span carries the traceId for correlation β you can join spans with logs using traceId when both are emitted from the same run.