In the previous part, we checked whether generated prose contains an answer and whether evidence supports its claims. We kept those checks separate from whether the answer reads well.
Now suppose those checks keep finding the same mistakes: assistance becomes leadership, a planned improvement becomes an achieved result, and a missing tool response becomes a completed action. We can patch the instructions after each failure. But how do we turn those patches into reusable policy, test whether they generalise, and retain the useful alternatives?
That is the application I want to explore with GEPA and decision models: use reflective evolutionary search to improve the criteria a model applies, while keeping the evaluator and operational constraints outside that search.
The expensive model helps design the policy. A decision model interprets the selected version at runtime. Code still owns facts, state transitions and permitted actions.
This is a proposed architecture and experiment, not a report of an implemented evolutionary engine or measured policy improvements.
GEPA, Genetic-Pareto, is a reflective optimisation method described in an ICLR 2026 paper. It runs a system, examines execution traces and evaluation feedback, proposes revised instructions, and tests the revision. Useful variants become parents for further attempts.
Think of an execution trace as the record of inputs, model outputs and tool interactions. An evaluation trace explains how the check reached its score: a failed assertion, compiler error or rubric finding. Reflection can use the explanation instead of receiving only “score: 0”.
The paper keeps the underlying model weights fixed while evolving prompts. It also investigates code optimisation. The current GEPA project explicitly supports evolving arbitrary text parameters through optimize_anything. Applying evolution beyond a chatbot prompt is therefore already part of GEPA's scope.
The specific proposal here is to make versioned decision criteria the target, use grounded checks as feedback, and investigate a population of specialist policies at runtime. The paper's results do not establish that this combination works.
Jev and Nimble let us supply a situation, a question and descriptions of allowed choices. A decision model selects a choice and returns associated scores.
Those descriptions affect how the same input is classified. We can change them without retraining the model. Here policy means the application's decision rubric, not a new set of neural-network weights.
For document field resolution, an initial policy might be:
policy: document_field_resolution
version: 1
decisions:
accept: The value is supported by the evidence and passes required checks.
review: Evidence is missing, ambiguous, or supports competing values.
reject: The value contradicts explicit evidence or fails required checks.
The application defines what those outcomes mean. A proposed revision must not quietly turn accept into “plausible enough”. Hard validation still runs in code before the model; mentioning it in the rubric does not delegate its enforcement.
The runtime call resembles the one from the previous part:
var decision = await nimble.ClassifyAsync(
text: evidenceAndProposedValue,
instructions: policy.Instructions,
criteria: policy.Criteria);
nimble is the existing demo's decision adapter. The new parameter is the policy object: instructions and criteria become a versioned artefact rather than strings scattered through application code.
Longer descriptions may also increase runtime cost. A decision model has a narrower output contract than a general writer; that does not automatically make every policy cheap.
Consider this synthetic source:
I helped implement a C# retry queue. Its effect has not been measured.
And a proposed rewrite:
I led the development of a C# retry queue that improved reliability.
A useful diagnostic retains the separate findings:
coverage:
contribution_mentioned: true
grounding:
leadership_claim: unsupported
reliability_improvement: unsupported
communication:
readability: acceptable
expected_decision: review
“Helped” does not establish leadership, but it does not logically rule leadership out either. Likewise, no measurement does not prove that reliability failed to improve. The rewrite goes beyond the available evidence. If the source explicitly said “I did not lead”, the leadership claim would be a contradiction.
That distinction matters to the policy. With the definitions above, missing support goes to review; an explicit conflict goes to reject. In a résumé writer, review can mean retaining the supported source wording and continuing generation, with an optional question to enrich the record.
The reflection model can propose a clearer criterion:
Accept an ownership or outcome claim only when the evidence establishes it. Plausible consequences of an implementation are not observed results. Missing support requires review; explicit conflict requires rejection.
That is a candidate improvement, not an established one. Test it on other ownership and outcome examples, including valid leadership claims and supported qualitative results. Otherwise the policy may merely learn to distrust useful detail.
The earlier article separates three questions:
| Check | Question |
|---|---|
| Coverage | Is the requested information present, or is its absence explained? |
| Grounding | Does the selected evidence support each claim? |
| Communication | Is the explanation clear and useful to its reader? |
Those findings provide feedback. They are not interchangeable rewards. A readable unsupported claim must not compensate for failed grounding, and a policy that reviews everything must not win merely by avoiding false approvals.
I would combine fixed ground-truth decisions with deterministic checks and diagnostic model findings. NLI—natural-language inference—can help identify support, contradiction or insufficient information. It remains a fallible signal: our earlier fixtures exposed mistakes in those distinctions.
Freezing an NLI judge does not make it ground truth. The search might discover language that exploits its weaknesses. Keep independent adjudicated examples, explicit invariants and a final test set outside reflective feedback. Do not let the mutator rewrite labels, evaluator prompts, evidence records or the success definition.
GEPA needs a numeric objective for comparisons, but that does not require discarding the component findings. Fix the scoring rule, retain diagnostics, and use separate admission constraints for unacceptable errors. Measure useful resolutions and review burden alongside false approvals.
flowchart TD
I[Input evidence and current state] --> H[Deterministic checks]
H -->|Valid for semantic assessment| N[NLI and other diagnostic signals]
N --> D[Decision model with adopted policy]
D --> G[Code applies permissions and workflow rules]
G --> O[Outcome and trace]
H -->|Hard failure| F[Code handles invalid input]
O --> E[Independent evaluation]
E --> R[Offline reflection and policy proposals]
R --> T[Test candidates within budget]
T --> P[Versioned policy registry]
P -->|Explicit adoption| D
Reflection happens offline or asynchronously. A production request does not need to breed policies before answering. It uses an adopted policy version; feedback becomes evidence for a later search run.
The registry needs more than a winning string. Retain criteria, parent versions, evaluator and dataset revisions, model identity, scores by case class, and the decision to adopt it. A model change can alter how identical criteria behave, so it needs evaluation too. Rollback should restore the complete runtime configuration.
This follows the Ten Commandments: a model proposes interpretations, while ordinary software controls state and side effects. The optimiser does not gain permission to change that boundary.
GEPA's paper uses instance-level candidate selection: identify candidates that achieve the best score on individual evaluation instances, prune dominated parent candidates, and weight parent selection by those wins. The search eventually returns the candidate with the best aggregate selection-set score. It does not establish a production router over a collection of specialists. See Section 3.1 and Algorithms 1–2 in the paper.
For the proposed engine, I would also test keeping complementary policies for deployment. Suppose admitted policies have these entirely hypothetical label-agreement rates:
| Policy | Totals cases | Date cases | Supplier cases |
|---|---|---|---|
| Arithmetic emphasis | 98% | 76% | 80% |
| Layout emphasis | 82% | 96% | 85% |
| Semantic emphasis | 86% | 83% | 97% |
None beats every other policy across all three columns. This is the useful intuition behind a Pareto frontier: alternatives can survive because each offers an advantage. The best average hides that arithmetic and layout specialists may still be valuable.
A router could select a specialist using observable input characteristics. Its experts would be policies or strategies rather than separately trained networks. Routing is another fallible decision, however. It cannot know which policy will succeed just because our evaluation table does. Test the router and specialists together against the best single-policy baseline, including routing cost and mistakes.
Maintaining diversity across explicit task classes, latency, review burden and error constraints is an extension to investigate, not a claim about the paper's exact selection algorithm.
The useful boundaries are policy, evaluation, mutation and selection. These are proposed application contracts, not a GEPA .NET SDK:
public sealed record DecisionPolicy(
string Id, string Version, string Instructions,
IReadOnlyDictionary<string, string> Criteria);
public sealed record EvaluationResult(
string ExampleId, double Score, bool FalseApproval,
IReadOnlyList<string> Diagnostics);
public interface IPolicyEvaluator<T>
{
Task<EvaluationResult> EvaluateAsync(
DecisionPolicy policy, T example, CancellationToken ct);
}
public interface IPolicyMutator
{
Task<DecisionPolicy> MutateAsync(
DecisionPolicy parent,
IReadOnlyList<EvaluationResult> feedback, CancellationToken ct);
}
public interface IPolicySelector
{
DecisionPolicy SelectParent(
IReadOnlyList<DecisionPolicy> population,
IReadOnlyDictionary<string, IReadOnlyList<EvaluationResult>> results);
}
The evaluator runs a candidate against an example and returns both a score and an explanation. The mutator receives training feedback and proposes revised criteria. The selector keeps the search from repeatedly modifying only the current favourite. Policy and result records supply the fourth boundary: the artefacts passed between those operations.
For an initial implementation, I would connect the existing GEPA engine to a .NET evaluator. The current Python API accepts an evaluator that returns a score plus diagnostic information:
from gepa.optimize_anything import optimize_anything
def evaluate_policy(candidate, example):
report = run_dotnet_evaluator(candidate, example)
return report["score"], {"feedback": report["diagnostics"]}
result = optimize_anything(
seed_candidate=seed_policy_yaml,
evaluator=evaluate_policy,
dataset=training_examples,
valset=selection_examples,
config=search_config,
)
run_dotnet_evaluator is the application bridge to implement, not a GEPA function. It parses and validates candidate policy text, runs the decision model, and scores it against the fixed example. Setup supplies the datasets and a search_config selecting the GEPA engine, reflection model and evaluation budget. A custom adapter offers more control over traces when needed.
This leaves the .NET application owning the runtime and registry without implementing a second evolutionary search. The C# contracts describe its boundaries; they do not reproduce GEPA's algorithm.
Changing choice descriptions is the smallest experiment. A later search could propose another intermediate question:
question: highest_supported_outcome_stage
choices:
planned: Only an intention is established.
implemented: Implementation is established, but measurement is not.
measured: A measurement is reported, whether positive or negative.
unknown: The evidence does not establish a stage.
That may help separate “designed to reduce latency” from “reduced latency”. Measurement is not success, and implementation is not production deployment; those need their own distinctions when relevant.
New questions, context selection, sequencing and abstention conditions change the task itself. Evaluate the whole configuration, including additional calls and missing-context errors. A score of 0.8 from the new choice set is not directly interchangeable with 0.8 from the old one. Revisit thresholds rather than inheriting them accidentally.
The possible evolution targets extend beyond criteria:
| Target | Example | Runtime |
|---|---|---|
| Criteria | Distinguish unsupported from contradictory claims | Decision model |
| Routing | Select a specialist for an ambiguous total | Router plus policy |
| Rules | Check arithmetic under a known invoice schema | Deterministic code |
Suppose repeated failures suggest checking net + tax == total. Once the schema guarantees those are the only components, with matching currency and an agreed rounding policy, that relationship belongs in code. A model need not reconsider the arithmetic for every invoice.
But a passing sum does not establish that OCR read the correct values or that a number belongs to the total field. Competing candidates can both pass arithmetic. Preserve evidence and review unresolved ambiguity.
I would initially restrict executable proposals to a small, reviewed rule language, then test their preconditions and counterexamples. Turning a language judgement into a reliable predicate is a separate engineering step. Passing a finite test set does not prove equivalence for every future input.
If useful rules can be qualified and adopted, some requests may avoid model inference. That is a hypothesis to measure, not an automatic consequence of evolution.
The previous part's six coverage examples and twelve support examples are smoke tests, far too small to establish generalisation. Begin with the answered, unknown and missing criteria, then build a larger adjudicated corpus covering ownership, outcomes, negation and time.
Compare the original rubric, a manually revised rubric, an evolved single-stage rubric and an evolved multi-stage configuration. Give competing searches comparable evaluation budgets and repeat runs with different seeds. Keep the runtime model fixed initially, then assess transfer to another model separately.
Split related documents, roles and paraphrase families together. Use training examples for reflection, a selection set for candidate comparisons, and an untouched final test set. Repeated selection can overfit its set even when its raw examples are hidden; final test feedback must not become another mutation opportunity.
Report held-out decision accuracy, false approvals, useful resolutions, abstentions, per-class regressions, runtime latency and the cost of optimisation. A specialist population must beat a single policy end to end, not merely display attractive component scores.
Finally, hold input characterisation fixed. Behavioural vectors and semantic ledgers can supply reusable features and evidence. Changing how inputs are represented at the same time as changing policy would make the source of an improvement difficult to identify. Feature engineering and embedding use also need not involve training new representations.
The research question is specific: can grounded reflective feedback improve a fixed decision model's criteria on unseen cases, and can specialist policies earn back their routing cost? The next question is whether qualified rules can remove some of that inference entirely. Both leave the same operational boundary intact: the model may help design and interpret policy, while code remains responsible for what the system is allowed to do.
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.