Predict call, carrying the rendered prompt, the parsed output, and a request fingerprint. Replay serves a later run back from that recording instead of a live provider, and exports project a finished trace onto external observability and training conventions. The dsrs CLI that checks, formats, and serves .dsrs artifacts has its own page: CLI.
Recording and replaying a run
Wrap any call incapture to get the run back as a value, then persist it as JSONL:
ReplayMode::Strict proves the pipeline still behaves exactly as recorded: every call is served from the log, with zero provider calls.
ReplayMode::UntilDivergence replays the unchanged prefix free and goes live only from the first call your change actually touches:
Capture
capture records every Predict call on the current task into a Trace while the scope is active.
capture(f)runs the closure and returns its result plus the recordedTrace.capture_with_meta(meta, f)is the same with caller-provided rollout metadata (TraceMeta: rollout input, candidate hash, free-form tags). A missingtrace_idor start time is minted at scope start.
Trace::to_jsonl and Trace::from_jsonl.
What a span records
One span is onePredict invocation. At a high level it records:
- Who ran: the component name (the same name the params system uses, so spans join back to tunable slots) plus a per-component sequence number;
(component, seq)is unique per trace and is what replay keys on. - What went in: the rendered prompt (an interned system-and-demos prefix plus the live suffix), the typed input fields as JSON, and the redacted model config.
- What happened inside: ordered events, one
Exchangeper provider round-trip and oneToolRunper tool execution. - What came out: the raw assistant text, the parsed output fields, aggregated token usage, and any error (kinds:
lm,parse,tool,cancelled). - A request fingerprint:
request_hash, a stable hash over the redacted model config plus the full rendered prompt. This is the replay key and the determinism check. - Timing and completeness: start time, duration, and a
completeflag (false when the span was truncated or redacted; replay refuses incomplete spans).
Replay
replay serves Predict calls from a recorded trace instead of a live provider: each call’s request_hash is compared against the next recorded span for its component, and on a match the recorded output is served with zero API calls and no tool execution.
Modes
ReplayReport fields
Replay error kinds
Replay scoping mirrors capture: task-local, not inherited by spawned subtasks, innermost scope wins. Compose replay outside with capture inside to record a counterfactual rollout while serving its unchanged prefix from the base trace.
Exports
Exports are pure serialization-side projections of a finishedTrace: no new capture machinery, no external dependencies. Both live under dspy_rs::trace.
Exports serialize traces after a run finishes. For live, human-readable console output while a program runs, call
init_tracing (documented on Utils): it installs a process-global pretty tracing subscriber (respecting RUST_LOG, defaulting to dspy_rs=debug) and is independent of trace capture.OpenTelemetry
Trace::to_otel_spans(include_content: bool) maps a trace onto OpenTelemetry GenAI semantic conventions as plain serializable structs in the OTLP/JSON wire shape (proto3 JSON mapping: camelCase keys, 64-bit integers as decimal strings, ids as lowercase hex), with no OpenTelemetry dependency. Trace::to_otlp_json(service_name, include_content) wraps those spans in a complete resourceSpans envelope that any OTLP/HTTP collector (Jaeger, Tempo, otel-collector) accepts at POST /v1/traces as-is.
Prompt, completion, and tool payloads are opt-in via
include_content, mirroring OTel’s GenAI content-capture switch: with include_content: false the spans carry identity, usage, and timing attributes only, so they stay exportable to shared collectors without leaking prompt text.
The emitted types, all Serialize structs:
The OTLP enum constants are exported as plain integers:
SPAN_KIND_INTERNAL = 1, SPAN_KIND_CLIENT = 3, STATUS_CODE_ERROR = 2.
ToolRun events record only their duration, so tool child spans start at their parent span’s start time: durations are exact, offsets within the parent are not.RL dataset
Trace::to_rl_rollout() projects the trace onto the Agent Lightning / verifiers rollout convention: one rollout as message lists plus a reward plus per-subcall transitions. It returns None when no eval was recorded (trace.outcome.eval unset); a rollout without a reward is not trainable. Because spans keep full Message structure (tool-call blocks, reasoning blocks), the projection needs no lossy text munging.
RlRollout serializes to a single JSON object; RlRollout::to_json_line() produces the one-line JSONL record RL trainers consume.
Spans whose policy emitted nothing (provider failures, cancelled spans: no
Exchange event) are omitted; they contribute no completion to train on. Parse-failure spans keep their transition: the emitted text exists even though it did not parse.
See also
- CLI:
dsrs servereturns the capture-scope trace artifact fromPOST /run?trace=1 - Utils:
init_tracingfor live pretty console output - Optimizers: traces as the evidence base for reflective optimization
- Evaluation: the evaluation loop that hands each rollout’s trace to your metric
- Example: 24-frontdesk-replay.rs, capture, strict replay, and until-divergence replay end to end
- Example: 12-tracing.rs, scoped trace capture for a composed module
- Example: 17-pretty-tracing.rs,
init_tracingoutput against an offline LM
