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. 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). - What it was worth (optional): a span-level
Eval, present only when a metric assigned per-span credit throughTypedMetric::evaluate_spans(see Evaluation). Demo harvesting prefers it over the whole-rollout score; the field is omitted from the JSONL entirely when absent, so eval-free traces serialize exactly as before. - 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.
Traces serialize after a run finishes (
Trace::to_jsonl). 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. There are no built-in exporters — the JSONL trace format is stable and self-describing, so external projections (observability, RL datasets) are serialization-side work on top of it.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
