tinyagents 2.1.0

A recursive language-model (RLM) harness for Rust.
Documentation
# graph::observability

Durable observability for the graph runtime — journals, status stores, and
the journaling event sink.

The live `graph::stream` layer emits transient `GraphEvent`s into an
in-process `GraphEventSink`. This module makes that history **durable and
correlatable** so a UI, supervisor, or test can reconstruct a recursive graph
run tree after the fact.

`CompiledGraph` wires into this module through builder-style
`with_status_store` and `with_event_journal`; both are opt-in and default off
so existing runs are unchanged.

## Public surface

- `GraphObservation` — a durable envelope pairing a `GraphEvent` with its run
  lineage (`run_id` / `parent_run_id` / `root_run_id`), `graph_id`,
  `checkpoint_id`, subgraph `namespace`, `step`, `offset`, and timestamp. This
  is the unit everything else in the module is built from.
- `GraphEventJournal` (trait) — an append-only, offset-addressable journal of
  observations.
  - `InMemoryGraphEventJournal` — in-process implementation for tests.
  - `StoreGraphEventJournal<A>` — store-backed implementation; stream key is
    the run id, so a `harness::store::AppendStore` durably persists a run's
    observations.
- `GraphStatusStore` (trait) — a compact "what is running now?" surface over
  `graph::GraphRunStatus`.
  - `InMemoryGraphStatusStore` — in-process implementation with a
    `thread_id → run ids` index so `list_by_thread` is proportional to that
    thread's runs. Unbounded by default; `with_max_runs(n)` caps retained
    runs, evicting the oldest terminal run first (oldest live run when none
    is terminal).
- `JournalGraphSink` — a `GraphEventSink` that wraps each emitted event into a
  `GraphObservation` and appends it to a journal, optionally also forwarding
  to a live `inner` sink (`with_lineage`, `with_thread`, `with_namespace`,
  `with_inner` builders).
- `GraphLatencyMetrics` — per-step/per-node timing rollups derived from a
  run's observations (`from_observations`, `average_step_ms`,
  `average_node_ms`).
- `GraphHealthSummary` — per-node success/failure counts derived from
  observations — node-level **tool health** telemetry (`from_observations`,
  `from_status`).
- `GraphLangfuseExporter` (`langfuse/`) — exports a run's observations to
  Langfuse, turning supersteps and nodes into timed spans (failures promoted
  to `ERROR`) and attaching the health summary to the trace. It emits a
  structural graph-run span (`{trace}:run:{run_id}`) that steps, subgraphs, and
  point events nest under. It shares the harness `LangfuseClient` transport and
  defaults its `traceId` to the run's `root_run_id`; because the graph-run span
  uses the same id scheme the harness exporter parents its agent run spans to,
  a graph run and the agent/tool runs its nodes spawn nest into one trace tree.

## Persistence bridge

`JournalGraphSink` bridges the synchronous `GraphEventSink::emit` hook to the
async journal API through a background `AppendWorker`: `emit` hands the
observation to a bounded channel drained on a dedicated thread, so it never
blocks the executor on I/O. Persistence is **best-effort**: a full queue drops
(and counts) the overflow, backend errors are reported to stderr rather than
propagated, and neither aborts the run. The executor calls `GraphEventSink::flush`
after the terminal run event (and callers can call it directly) to block until
the durable log has caught up. Do not rely on the sink for delivery guarantees
stronger than "usually persisted, never run-blocking."

## Files

| File | Role |
| --- | --- |
| `types.rs` | Every public type: `GraphObservation`, journal/status-store traits and in-memory impls, `JournalGraphSink`, latency/health rollups. |
| `mod.rs` | Behavioral code: rollup computation, journal/store/sink impls. |
| `langfuse/` | `GraphLangfuseExporter` and its span-construction logic. |
| `test.rs` | Unit tests (journal round-trips, sink lineage, latency/health rollups). |

## Operational constraints

- `StoreGraphEventJournal` keys the stream by run id; reusing a run id across
  logically distinct runs interleaves their observations.
- The health summary's failure counts are node-scoped, not step-scoped — a
  node retried and eventually succeeded still contributes its earlier
  failures to `GraphHealthSummary`, by design (it is a *tool health* signal,
  not a final-status signal).
- The Langfuse exporter shares transport code with
  `harness::observability::LangfuseClient` (via `harness::observability`'s
  crate-visible `clean_nulls`/`iso_ms` helpers); keep timestamp/null-pruning
  behavior in sync between the two if either changes.