agentkit-reporting 0.2.2

Reporting observers and event consumers for agentkit loops.
Documentation
# agentkit-reporting

Observers for turning loop events into logs, summaries, and transcript views.

This crate provides [`LoopObserver`] implementations for [`agentkit-loop`].
Instead of baking reporting into the driver, you attach one or more reporters
to the loop and they react to every `AgentEvent` that flows through it.

## Included reporters

| Reporter             | Purpose                                                    |
| -------------------- | ---------------------------------------------------------- |
| `StdoutReporter`     | Human-readable bracketed log lines (`[turn] started ...`)  |
| `JsonlReporter`      | Machine-readable newline-delimited JSON envelopes          |
| `UsageReporter`      | Aggregated token counts and cost totals                    |
| `TranscriptReporter` | Growing snapshot of conversation items                     |
| `CompositeReporter`  | Fan-out wrapper that forwards events to multiple reporters |

## Quick start

Compose several reporters with `CompositeReporter` and hand it to the loop:

```rust
use agentkit_reporting::{
    CompositeReporter, JsonlReporter, StdoutReporter,
    TranscriptReporter, UsageReporter,
};

// Build a composite reporter that fans out to all four reporters.
let reporter = CompositeReporter::new()
    .with_observer(StdoutReporter::new(std::io::stderr()).with_usage(false))
    .with_observer(JsonlReporter::new(Vec::new()).with_flush_each_event(false))
    .with_observer(UsageReporter::new())
    .with_observer(TranscriptReporter::new());

// Pass `reporter` as the observer when constructing the agent loop.
```

## Accessing outputs after the loop

Reporters that accumulate state (`UsageReporter`, `TranscriptReporter`,
`JsonlReporter`) expose accessors for reading back data once the loop
finishes:

```rust
use agentkit_reporting::{UsageReporter, TranscriptReporter, JsonlReporter};

// Usage totals
let reporter = UsageReporter::new();
// ...run the loop...
let summary = reporter.summary();
println!(
    "tokens: {} in / {} out, turns: {}",
    summary.totals.input_tokens,
    summary.totals.output_tokens,
    summary.turn_results_seen,
);

// Transcript items
let reporter = TranscriptReporter::new();
// ...run the loop...
for item in &reporter.transcript().items {
    println!("{:?}: {} parts", item.kind, item.parts.len());
}

// JSONL buffer
let mut reporter = JsonlReporter::new(Vec::new());
// ...run the loop...
let jsonl = String::from_utf8(reporter.writer().clone()).unwrap();
let errors = reporter.take_errors();
assert!(errors.is_empty(), "reporting errors: {:?}", errors);
```

## Writing to a file

`JsonlReporter` and `StdoutReporter` accept any `std::io::Write`
implementation, so you can point them at files, network sockets, or
in-memory buffers:

```rust,no_run
use agentkit_reporting::JsonlReporter;
use std::io::BufWriter;
use std::fs::File;

let file = File::create("events.jsonl").expect("open file");
let reporter = JsonlReporter::new(BufWriter::new(file));
```

## Adapter reporters

For expensive or async reporting, wrap an inner reporter in one of the
provided adapters:

| Adapter            | Purpose                                              |
| ------------------ | ---------------------------------------------------- |
| `BufferedReporter` | Enqueues events and flushes in batches               |
| `ChannelReporter`  | Forwards events to another thread via `mpsc::Sender` |
| `TracingReporter`  | Emits `tracing` events (requires `tracing` feature)  |

```rust
use agentkit_reporting::{BufferedReporter, JsonlReporter};

// Flush to the JSONL writer every 128 events instead of one-at-a-time.
let reporter = BufferedReporter::new(
    JsonlReporter::new(Vec::new()).with_flush_each_event(false),
    128,
);
```

```rust
use agentkit_reporting::ChannelReporter;

let (reporter, rx) = ChannelReporter::pair();

std::thread::spawn(move || {
    while let Ok(event) = rx.recv() {
        println!("{event:?}");
    }
});

// Pass `reporter` to the agent loop.
```

### TracingReporter

`TracingReporter` bridges agent events into the `tracing` ecosystem. It
is gated behind the `tracing` feature to keep the dependency opt-in:

```toml
agentkit-reporting = { version = "0.2.2", features = ["tracing"] }
```

```rust,ignore
use agentkit_reporting::TracingReporter;

let reporter = TracingReporter::new();
```

Events are emitted under the `"agentkit"` target at levels that match
their severity (INFO for lifecycle, DEBUG for usage/compaction, TRACE for
content deltas, WARN/ERROR for problems).

## Failure policy

Reporter failures are non-fatal by default. For reporters that can fail
(I/O, channel sends), implement `FallibleObserver` and wrap it in a
`PolicyReporter` to choose how errors are handled:

| Policy       | Behaviour                           |
| ------------ | ----------------------------------- |
| `Ignore`     | Silently discard errors (default)   |
| `Log`        | Print errors to stderr              |
| `Accumulate` | Collect errors for later inspection |
| `FailFast`   | Panic on first error                |

```rust
use agentkit_reporting::{ChannelReporter, FailurePolicy, PolicyReporter};

let (reporter, rx) = ChannelReporter::pair();
let reporter = PolicyReporter::new(reporter, FailurePolicy::Log);
```

## Error handling

`JsonlReporter` and `StdoutReporter` never panic on write failures.
Errors are collected internally and can be drained after the loop with
`take_errors()`. This keeps the `LoopObserver::handle_event` signature
infallible while still giving you full visibility into any I/O issues.