DSPy — metric-driven agent program optimization
This extraction answers WI-027’s bounded question: what can be claimed when an agent-capable
program is evaluated by a metric and an optimizer changes its prompt state. It follows the
evaluation/optimization template. The selected path is DSPy’s
ReAct module plus BootstrapFewShot; Evaluate and module saving establish the score and
artifact boundaries. This is not a benchmark, provider comparison, or claim that an optimized
program is generally better.
Snapshot and read boundary
Measured — The source is the public
stanfordnlp/dspy repository
at commit 80b118e52cb1f143a0d80d84685572000c59639e, read 2026-08-15. The checkout reports
3.3.0-26-g80b118e5; pyproject.toml declares package version 3.3.0, Python >=3.10, <3.15,
and the dependency set including litellm, diskcache, cloudpickle, and gepa.
Measured — The bounded checkout measurement was:
git -C C:\Temp\rungs-follow-on-20260815\dspy ls-files
563 tracked files
279 Python files
169 Markdown/MDX files
135 test paths
25 dspy/teleprompt paths
26 dspy/predict paths
These counts describe paths at the pin. They do not measure model quality, provider availability, token cost, or the number of programs that use any optimizer.
Documented — The pinned LICENSE
is MIT. The read set is deliberately narrow: pyproject.toml; dspy/predict/react.py;
dspy/teleprompt/bootstrap.py; dspy/evaluate/evaluate.py; dspy/primitives/module.py and
dspy/primitives/base_module.py; dspy/utils/dummies.py; and the executable tests
tests/teleprompt/test_bootstrap.py and tests/evaluate/test_evaluate.py.
Executed — On 2026-08-15, Python 3.13.3 ran py -3.13 -m compileall -q over those four
implementation files and two test files; the exit code was 0. The checkout had no pytest
module, so the test suite was read but not presented as executed evidence. No external model,
provider, benchmark, or network endpoint was called.
E1. Evaluation contract
| Element | Definition and owner | Pinned/executable evidence | Versioned input | Failure or missing-data rule |
|---|---|---|---|---|
| Task and sample | dspy.Example holds input/output fields; ReAct receives signature inputs and emits answer fields plus a trajectory | Implemented — ReAct.forward; tests construct Example values | Caller-owned examples and signature; no dataset version is generated by DSPy | Missing output is a metric/program concern; tool exceptions become trajectory observations |
| Execution environment/tools | Module invokes predictors under configured LM/settings; ReAct wraps callables as named tools | Implemented — Module.__call__; ReAct.__init__ | Python/package and LM configuration are external inputs | Tool state, provider retries, network access, and sandboxing are outside this path |
| Evidence log | ReAct returns a structured trajectory; Bootstrap reads dspy.settings.trace; Evaluate retains (example, prediction, score) tuples | Implemented — ReAct trajectory; BootstrapFewShot._bootstrap_one_example; EvaluationResult | Trace is in-memory for the call/compile; persistence is caller-owned | A normal call does not create a durable audit record |
| Scoring function/judge | A caller metric compares an example and prediction; Bootstrap may receive the trace as a third argument | Implemented — metric invocation; Evaluate invocation | Metric code and threshold are caller inputs; no judge model is required | Exceptions are counted by the executor/bootstrap error policy; evaluation substitutes failure_score (default 0.0) |
| Aggregation/uncertainty | Evaluate sums per-example scores and reports a percentage; EvaluationResult.results retains row-level evidence | Implemented — Evaluate aggregation | Devset, metric scale, and row count are caller-owned | No confidence interval, variance, calibration, or uncertainty estimate is produced |
| Optimizer feedback/selection | BootstrapFewShot accepts successful teacher traces as demos and adds labeled examples to the student | Implemented — compile/_bootstrap; _train | Trainset, teacher/student structure, metric, demo limits, and rounds | A false metric result is not bootstrapped; no independent held-out score is required by this optimizer |
Runtime loop: an agent program before optimization
Implemented — ReAct converts a signature into a reasoning/action predictor and adds a
finish tool. forward loops for max_iters (default 20 in the implementation), records
thought, tool_name, tool_args, and observation fields, executes the selected callable, and
then runs a separate extraction predictor to produce the declared outputs
(constructor,
loop).
Implemented — A tool exception is converted to an error string in the trajectory and the loop can continue. An invalid tool selection or a context-window exception ends the action loop before extraction; context overflow gets up to three truncation attempts, removing the oldest four trajectory keys per attempt (failure path, truncation).
Boundary — The trajectory is an explicit output of ReAct, but it is not automatically a
durable trace, approval record, or tool-side transaction log. A tool may have applied an external
side effect before returning an error; the module does not compensate for it. These are
implementation consequences of this path, not guarantees about every DSPy module or tool.
Optimization loop: BootstrapFewShot
Implemented — compile(student, teacher, trainset) first creates a reset student and a deep
copy teacher. If needed, it gives the teacher up to max_labeled_demos labeled examples, checks
that student and teacher have matching predictor structure, and then bootstraps examples
(compile preparation).
Implemented — For each training example, _bootstrap makes at most max_rounds attempts and
stops after max_bootstrapped_demos successful examples. Each attempt executes the teacher with a
fresh trace; rounds after the first copy the LM with rollout_id=round_idx and temperature=1.0
to bypass caches. The metric receives (example, prediction, trace). A numeric metric is accepted
when it meets metric_threshold; a truthy metric is accepted when no threshold is supplied
(bootstrap loop).
Implemented — A successful trace is converted into augmented dspy.Example demonstrations
for the matching predictor. Unsuccessful training examples become validation, are shuffled with
random.Random(0), and are sampled as additional labeled demos. The returned student receives
those demos and _compiled=True; BootstrapFewShot changes prompt examples, not model weights or
the ReAct source (training result).
Implemented — The pinned tests make the contract executable in principle: test_bootstrap_effectiveness
checks the compiled flag, selected demo, and prediction using DummyLM(follow_examples=True);
test_error_handling_during_bootstrap checks propagation at max_errors=1; and
test_validation_set_usage checks that the leftover examples are placed in the compiled demos
(tests).
The extraction read these tests and syntax-checked them, but did not run pytest.
Evaluation, artifact, and provenance boundaries
Implemented — Evaluate runs program(**example.inputs()), calls the metric, and uses a
ParallelExecutor with configurable num_threads, max_errors, and progress settings. It keeps
one result row per devset example, substitutes dspy.Prediction() plus failure_score for a failed
row, and computes 100 * sum(scores) / len(devset). It can serialize the row table to CSV or JSON
when the caller supplies a path (evaluation path).
Implemented — The evaluation tests cover exact-match success and failure, one- and multi-thread
execution, cancellation, callbacks, and JSON/CSV output using DummyLM; they establish those
bounded behaviours, not model-provider reliability or score quality
(test file).
Implemented — Module.save has two materially different artifacts. State-only JSON/PKL stores
dump_state() plus dependency metadata and must be loaded into a compatible program. Program saving
creates a directory containing program.pkl and metadata.json; metadata records Python, DSPy,
and cloudpickle versions. Program loading requires allow_pickle=True and warns that untrusted
pickle can execute code. JSON is therefore the safer state interchange, while pickle is the
architecture-preserving but code-executing boundary
(save,
load).
Boundary — A serialized compiled program does not by itself contain the dataset, metric source, provider response history, tool side effects, external files, credentials, or a held-out score. Dependency metadata helps detect drift; it does not make an LM provider deterministic.
Reproducibility, leakage, and cost
| Factor | Evidence at the pin | Claim boundary |
|---|---|---|
| Source/runtime | dspy 3.3.0, Python >=3.10,<3.15, exact SHA above | Replays this source/API shape, not future releases |
| Model/provider | dspy.settings.lm is configured by the caller; tests use deterministic DummyLM | Provider, model version, sampling, tool network, and response drift remain external |
| Seed/cache | Bootstrap uses fixed random.Random(0) for validation/demo sampling; later rounds vary LM rollout_id and temperature | This does not seed provider sampling or guarantee identical traces; cache state is not an artifact |
| Data split | Bootstrap accepts one trainset; unsuccessful examples are called validation but are sampled back into demos | It is not a held-out test set. A caller must supply and protect an independent evaluation set |
| Parallelism/failures | Evaluate exposes threads and max errors; failures receive failure_score; Bootstrap counts errors and can re-raise at its limit | Timing, ordering, provider retries, and partial external effects can change a run |
| Calls/cost | max_iters, max_rounds, max_bootstrapped_demos, and evaluation set size bound this path’s logical work | Token, tool, network, and provider billing are not counted by the selected optimizer |
Counter-evidence — The strongest leakage risk is structural: BootstrapFewShot uses the same
caller-supplied trainset to generate teacher traces and to form the leftover validation pool, then
adds both successful traces and sampled leftovers to the compiled student. The optimizer has no
required independent holdout and no automatic leakage detector. Any improvement claim must therefore
be restricted to a separately versioned evaluation set, metric, model/provider, and run.
Counter-evidence — Evaluate aggregates arbitrary metric values as a percentage and reports no
uncertainty. A score can be reproducible yet invalid for the task, or can hide per-example failures;
the retained result rows are necessary evidence, not proof of metric validity.
Failure and recovery matrix
| Failure | Pinned behaviour | Recovery/owner |
|---|---|---|
| Tool raises | Error text is appended as an observation and the ReAct loop may continue | Tool/module author decides whether to retry, stop, or compensate |
| Invalid tool or context overflow | Action loop ends (after bounded truncation attempts) and extraction still runs when possible | Caller controls max_iters, prompt/tool validation, and durable side-effect recovery |
| Metric false/under threshold | Trace is not accepted as a bootstrapped demo | Caller supplies metric, threshold, and a separate validation/test policy |
| Bootstrap exception | Error count increments; reaching max_errors re-raises | Caller chooses max errors and handles partial compile state |
| Evaluation exception | Executor records a failed row using failure_score or stops at max_errors | Caller inspects row-level results and failure policy |
| Artifact load drift | Version mismatches warn; pickle loading is refused unless explicitly allowed | Pin source/dependencies and treat pickle as trusted code only |
Candidate pattern consequences (deferred to WI-028)
| Candidate | Evidence | Provisional disposition |
|---|---|---|
evidence | Implemented — Evaluate retains example, prediction, and metric rows; ReAct exposes a structured trajectory | Candidate only if the retained rows and trace are persisted with dataset/model/metric identity |
semantic-gate | Implemented — a caller metric decides whether a trace becomes a demonstration | Candidate; metric validity and leakage remain application-owned |
bounded-loop | Implemented — ReAct max_iters, Bootstrap max_rounds/demo limits, and executor max errors bound this path | Candidate; bounds constrain attempts, not external tool cost or side effects |
explicit-output | Implemented — compiled state/demos and optional CSV/JSON evaluation output are explicit artifacts | Candidate; serialized state omits data, credentials, and external effects |
structural-gate | Implemented — Bootstrap requires matching predictor structures and marks the student compiled | Candidate; structural compatibility is not semantic correctness |
test-substitution | Opinion — the metric functions as a selection gate, not a replacement for an independent test | Keep separate until WI-028 compares Inspect AI’s evaluation contract |
No catalogue, module, or CLI file changed. WI-021 owns Inspect AI’s evaluation evidence, and WI-028 owns cross-track reconciliation and any catalogue decision.