1. Why an evaluation ruler is not enough to rewrite a Skill

Chapter five prepared the comparison: the same generated-code-testing Skill faces fixed tasks, train supplies rewrite evidence, validation steers candidate search, and holdout compares the frozen baseline with the final candidate only after search stops. It now seems that one command should be enough: hand the Skill to an optimizer and ask for a better version.

An optimizer does not receive a blank page, though. One Agent run contains the user's task, Skill instructions, tool-control flow, model output, and scoring rule. If all of that is assembled as one temporary string, an evaluator can report a failure but cannot answer two basic questions: which text may change, and how is the selected text installed back into the program?

Keep following the same example. The old Skill already says to regenerate code after a schema change and then run tests, but the Agent still forgets to inspect the generated diff. We want the optimizer to change that working instruction. It must not change the user's schema, rewrite Python control flow, or quietly edit the rubric to make the score easier. Search therefore needs four boundaries before it begins:

Fixed inputs and outputs: a task enters and a result leaves
Fixed execution structure: how the program calls models and tools
Searchable parameter: the Skill instruction for doing the task
Fixed evaluator: how the same task produces a score and failure reason

Each boundary protects something different. The first two keep every candidate on the same job, the third names where change is allowed, and the fourth keeps old and new versions under the same ruler. Without an explicit third boundary, an optimizer may tune a surrounding prompt while the Skill body never changes. If the fourth boundary moves with the candidate, a higher score is no longer comparable.

This chapter follows one evidence chain. How the old Skill becomes an optimizable parameter; how a failure becomes a new instruction; how that candidate is rerun and selected; how winning text returns to a file; and how constraints, holdout, and human review gate adoption. A link that exists only in a README, class name, or comment is not a closed loop.

The previous chapter already explains tasks, rubrics, and the permissions of the three data splits, so this chapter recalls them only when the execution requires it. We will first use DSPy to separate program structure from searchable parameters, then follow GEPA from trace and feedback to rewritten text, and finally audit the latest fixed Hermes Agent Self-Evolution snapshot against that target flow.

2. Before GEPA, identify what DSPy makes optimizable

We now have tasks and a ruler, but one interface is still missing: what is the optimizer allowed to change? If an application concatenates one long prompt string and sends it directly to a model, a low score does not tell an optimizer which text is the task contract, which text is runtime input, or how a revision should be installed back into the program.

DSPy addresses that middle layer. It is not one search algorithm. It is an LM programming framework: declare inputs, outputs, and task instructions; compose one or more calls into a module; then pass the program, examples, and metric to an optimizer. Prompt writing becomes compilation of a parameterized program.

Before reading class names, divide the contract into four boxes. The first two define how the program works, the third is what an optimizer may search, and the last only judges results:

fixed input and output fields: Signature
fixed execution structure: Module
searchable content: explicitly registered instructions / demonstrations
candidate judgment: Example + metric

DSPy does not rewrite arbitrary Python control flow, and it does not automatically treat every string as an optimizable parameter. Only exposed instructions, demonstrations, and similar parameters enter search. Input fields, Module control flow, and the metric remain developer-owned. The source that follows can be read by asking which box each line belongs to.

A task and skill enter a DSPy program defined by a Signature, Module, and metric; compile then lets BootstrapFewShot, MIPROv2, or GEPA optimize demonstrations, instructions, or reflective instruction revisions
DSPy defines the program and exposes parameters; BootstrapFewShot, MIPROv2, and GEPA search those parameters differently.

2.1 Put the generated-code task into DSPy

Keep the generated-code-testing Skill as the running example. The program should receive a schema change and repository state, return a verified repair, and allow an optimizer to replace the instruction that describes how generated code must be checked. That requires separate places for input and output, execution, evaluation cases, and scoring. DSPy assigns one object to each job.

Start with a Signature, the form for one language-model call. Its input field contains the schema change and failure scene, its output contains the repair and verification, and its instructions say "run the real generator, inspect the generated diff, then run the target tests." To optimize the Skill, an adapter must place that text in replaceable instructions rather than pass it as an ordinary input beside the code.

A Module decides how to execute the form, for example with one ChainOfThought predictor. The "schema changed but generated files are stale" task becomes an Example: repository state fills the input field and the scoring note stays with the case. Finally, a metric reads the result and the note to return a score; reflective optimizers such as GEPA can also consume textual feedback. Together these four objects tell an optimizer both which instruction may change and how to compare the result.

current skill
-> Signature.instructions
-> replaceable instructions

evaluation task -> Example
Module -> run one generated-code repair
metric
-> score and failure reason
compile -> revised program

The table can now compress the four source names without asking the reader to infer their order:

RoleIn the generated-code exampleResponsibility
SignatureSchema state is input, repair evidence is output, and instructions carry the SkillDeclare fields and the replaceable instruction
ModuleChainOfThought runs that SignatureFix call structure and control flow
ExampleOne generated-code failure and its rubricProvide a repeatable comparison
metricCheck regeneration, diff inspection, and the selected testsDefine what “better” means

The crucial boundary is parameter visibility. DSPy stores task instructions on Signature.instructions, and optimizers walk a Module's predictor tree to find parameters. Official source distinguishes predictor objects that are discoverable Parameters from plain Python attributes, which are not exposed automatically. See the Signature instruction contract and Module discovery rules.

compile() is therefore not a magic call that makes a model smarter. It accepts an explicitly structured student program, training examples, and a metric, then returns a copy with selected or revised prompt parameters. Model weights can remain unchanged. This gives us the right source-review question for Hermes: is the skill body a predictor instruction, or merely an input value?

2.2 The three optimizers do not change the same thing

The framework lets us choose a method by asking what the failure can teach. Start with the simplest case: the old skill is broadly correct, but the model lacks successful patterns to imitate. BootstrapFewShot runs training examples, keeps traces that satisfy the metric, and installs them as demonstrations on predictors. It mainly adds examples rather than systematically rewriting the task instruction.

When both the examples and the wording may be weak, MIPROv2 expands the search. It proposes multiple instruction and few-shot candidates, then uses validation scores to search their combinations. It can change both what the program says and which examples it shows, at the cost of evaluating more combinations.

When the run also preserves a failure scene such as "ran the generator but never inspected its diff," GEPA can read the trace and textual feedback, explain the failure, and revise predictor instructions. All three methods consume a DSPy program and evaluation data, but they change different parameters and learn from different signals. The table is now a compact replay:

OptimizerPrimary changeHow evaluation guides it
BootstrapFewShotSelect and insert demonstrationsKeep successful traces that satisfy the metric
MIPROv2Combine candidate instructions and demonstrationsPropose candidates, then search combinations by validation score
GEPARewrite predictor instructionsRead traces and textual feedback, reflect on failures, and propose revisions

The official BootstrapFewShot implementation installs qualified traces as predictor demos; MIPROv2 proposes both instruction and few-shot candidates before searching their combinations. See BootstrapFewShot and MIPROv2 candidate generation. GEPA also changes instructions, but it asks an LM to read failure evidence before writing the next one. We can now explain why that difference matters.

3. GEPA's key move is changing prompts with the reason for failure

Only now do we need GEPA. The paper keeps model weights frozen and searches over natural-language prompts. Each iteration selects a parent candidate, runs it on a minibatch, collects traces, scores, and textual feedback, then asks a reflection model to diagnose failures and propose a new prompt. Evaluated candidates return to the pool and the loop continues.

Keep the generated-code task in view. One reflective step is not free-form self-critique; it is a chain that can be executed again:

candidate P1 handles a test failure after a schema change
-> it runs the generator but never inspects the generated diff, so it scores poorly
-> feedback says the run cannot distinguish expected schema output from accidental files
-> the reflector adds an explicit "inspect the diff after generation" step
-> candidate P2 is produced
-> P1 and P2 rerun the same small task batch
-> only measured improvements return to the candidate pool

Read the next figure as "execute, diagnose, revise, retest." Textual feedback proposes a direction; another execution proves whether the revision helped. Without that final step, reflection is only a plausible explanation.

GEPA runs a candidate prompt on a minibatch, reads traces and feedback, reflects on failures, proposes new candidates, and maintains a Pareto candidate pool
A score says where performance is poor; traces and feedback help the reflector decide which instruction should change.

3.1 Traces turn "wrong" into "wrong at this step"

A score of 0.4 does not reveal whether a failure came from tool choice, ordering, a missed constraint, or output format. A trace exposes intermediate decisions. Textual feedback can add, for example, "the correct tool was called, but the agent did not use the fallback after an empty result." GEPA's reflective mutation reads that context and targets the failure mode. The algorithm and feedback interface are described in Section 3 of GEPA v2.

In the standalone GEPA implementation, this failure scene is not an arbitrary log blob. The adapter must return a per-example batch whose outputs, scores, and trajectories align. GEPA leaves trajectory contents opaque and asks the integration to interpret them. That boundary lets the engine optimize agents, RAG pipelines, or other text components without understanding each runtime.

Candidate = dict[str, str]

@dataclass
class EvaluationBatch:
    outputs: list[RolloutOutput]
    scores: list[float]
    trajectories: list[Trajectory] | None = None

def evaluate(
    batch,
    candidate: Candidate,
    capture_traces: bool = False,
) -> EvaluationBatch: ...

In EvaluationBatch, scores[i] says how well task i went and trajectories[i] preserves why. The reflection step later uses make_reflective_dataset to compress that trace into actionable feedback. Without this alignment, reflection would collapse into guessing from an aggregate score.

3.2 Why not mutate the overall winner forever?

One candidate may detect stale generation, another may catch generated files that should not be committed, and a third may choose the right test scope. Keeping only the highest average score can discard those local strengths too early. GEPA tracks a Pareto set based on per-example performance: it samples an example, then chooses among candidates that are strong on that example. Specialized candidates can remain parents instead of being erased by one temporary champion.

That is why Pareto appears in the name. It protects search diversity under a limited rollout budget. The paper's ablation finds that always mutating the current best is weaker than Pareto selection. It also notes that merging candidates is not always beneficial when prompt components depend on each other.

4. Walk one complete evolution before tracing framework wiring

We have now met the baseline, evaluation example, rollout, trace, candidate, DSPy, and GEPA. Recognizing the pieces is not the same as connecting the process. Keep the same generated-code Skill and replay how an old version becomes a deliverable version. Only then inspect how three frameworks pass data along that route.

4.1 How an old version becomes a deliverable candidate

  1. Freeze the starting point. Preserve the old skill, model configuration, task sources, and data split. The baseline must not move as candidates accumulate.
  2. Run the baseline. Execute the old version on training and validation tasks. Preserve each output, trace, score, and feedback so the system knows both how well it performs and where it fails.
  3. Propose a candidate. The optimizer selects a parent and a minibatch of failures. A reflection model reads the skill, traces, and feedback, then writes a targeted revision.
  4. Execute again. The candidate faces the same tasks. A structure, size, or safety violation rejects it immediately; only valid candidates reach score comparison.
  5. Continue the search. Validation scores and per-example strengths decide which candidates remain and which one becomes the next parent. The loop stops when budget expires or improvement stalls.
  6. Open holdout once. Only after selecting a candidate do old and new versions face tasks hidden throughout search. No stable improvement means no delivery.
  7. Hand the evidence to a person. Show the skill diff, baseline and candidate metrics, failed examples, and known limits. A reviewer may accept, reject, or revise it; the optimizer never overwrites the live skill directly.

The route contains three identities. The old version is the comparison point. A candidate is an unproven experiment. A delivered version earns that status only after holdout comparison and human review. Producing better-looking prose completes step three, not self-evolution.

4.2 Adapters translate three representations

With the route visible, framework wiring becomes easier to read. Hermes owns a skill file. DSPy wants a repeatable program whose optimizable text lives in predictor instructions. GEPA sees named text components plus an interface that executes them and returns per-example scores and traces. These are different data shapes, so an adapter has to translate between them.

GEPA's independent official implementation sends candidates to an adapter and receives scores, traces, and reflection material. It does not inherently know about DSPy Signatures or a Hermes SKILL.md. dspy.GEPA handles the DSPy side: it walks student predictors, turns pred.signature.instructions into seed components, runs the module with trace capture, and installs selected text into a new program. See dspy.GEPA.compile and the standalone GEPA adapter contract.

DSPy's translation reduces to the four lines below. The first line defines the search space: only predictor signature.instructions become seed components. The adapter turns candidates into traces and scores, GEPA performs the search, and the adapter installs the winning text back into a program.

seed_candidate = {
    name: pred.signature.instructions
    for name, pred in student.named_predictors()
}
result = optimize(seed_candidate=seed_candidate, adapter=adapter, ...)
new_program = adapter.build_program(result.best_candidate)

This excerpt from dspy.GEPA.compile gives us a concrete audit rule: to prove that text evolves, identify where it enters seed_candidate and where the corresponding best_candidate text is written back.

The adapter determines what actually evolves. Standalone GEPA can optimize arbitrary text through a custom adapter. The standard dspy.GEPA path seeds candidates from DSPy predictor instructions. A skill body supplied as a normal input does not become an optimizable parameter merely because it participates in the same forward call.

4.3 How the Hermes entry point expresses the target route

The Hermes CLI lays out ten intended stages: find a skill, prepare data, validate the baseline, configure DSPy, run GEPA, extract a revision, validate constraints, compare on holdout, report, and save. The main entry point is evolve_skill.py.

To make one skill repeatable on one task, SkillModule wraps it as a DSPy module. Each forward call supplies the skill body and task input to a ChainOfThought predictor and returns the model output. In plain terms, it turns "this skill performs this task" into a function that can be rerun, scored, and compared. See SkillModule.

class TaskWithSkill(dspy.Signature):
    skill_instructions: str = dspy.InputField()
    task_input: str = dspy.InputField()
    output: str = dspy.OutputField()

def __init__(self, skill_text):
    self.skill_text = skill_text
    self.predictor = dspy.ChainOfThought(self.TaskWithSkill)

def forward(self, task_input):
    return self.predictor(
        skill_instructions=self.skill_text,
        task_input=task_input,
    )

This wrapper already makes execution repeatable, but it also exposes the boundary the next section audits: self.predictor is a predictor DSPy can discover, while self.skill_text is an ordinary Python attribute that forward supplies through an InputField. Participating in one execution does not automatically place text in the seed_candidate.

Required stageEvidence to preserveCorrect failure behavior
Freeze baselineOriginal skill, source version, model configurationDo not silently move the baseline
Prepare evaluationTask sources, rubrics, split, and seedStop if the ruler is not credible
Reflect and mutateParent, examples, traces, feedback, and proposalKeep enough context to explain regressions
Gate candidatesStructure, size, tests, and benchmark resultsReject any hard-constraint failure
Compare holdoutBaseline and candidate on the same unseen setDo not deliver without improvement
Human deliveryDiff, metrics, limitations, and rollback versionNever overwrite a live skill directly

This table compresses the route from section 4.1 into a source-review ruler. A repository can contain a class for every stage without moving values through a complete evidence chain. "Implemented" is a statement about actual parameters, return values, gates, and delivery, not about filenames.

5. How far the current source goes: a prototype, not a closed loop

When returning to the Hermes repository, separate "a file exists," "it enters the main path," and "the target loop is demonstrated." The fixed snapshot first divides into three states:

StatusWhat the current source supports
In the main pathRead a skill, choose one data source, build DSPy examples, attempt optimization, compare holdout scores, and save output
Code exists but is not wired into the target pathThe richer LLM judge, test runner, and PR configuration do not gate this candidate search and delivery
Target route not demonstratedGEPA actually rewrites the skill body, every candidate passes hard gates, and an accepted result becomes a reviewable PR
Current source includes prototypes for loading a skill, building evaluation data, running an optimizer, and comparing holdout, while structure, tests, and delivery are not connected end to end
The README presents the target architecture; the current state appears only when parameters and calls are followed end to end.

Start with what exists. The repository can discover and parse skills, generate or import evaluation data, construct a DSPy module, invoke an optimizer, compare baseline and candidate on holdout, and save metrics with before-and-after artifacts. That is a real orchestration and data skeleton with unit tests around several components.

At fixed snapshot 0a929e3, however, several connections prevent it from being an end-to-end delivery loop.

5.1 The skill text is not clearly registered as a GEPA prompt parameter

SkillModule stores the body in an ordinary Python attribute, self.skill_text, then supplies it as the skill_instructions input field during forward. The orchestration later assumes optimized_module.skill_text contains a GEPA rewrite. The current source does not show a bridge that registers this plain attribute as a DSPy prompt parameter. From this call path alone, we cannot establish that GEPA rewrites the skill itself rather than predictor prompt state.

baseline_module = SkillModule(skill["body"])
optimized_module = optimizer.compile(baseline_module, ...)

# The orchestration reads the "evolved body" from a plain attribute.
evolved_body = optimized_module.skill_text

What is missing is a two-way mapping, not one assignment. Before optimization, the skill body must become predictor instructions; afterward, the winning instructions must become a SKILL.md body again. The current orchestration shows wrapping and extraction but not either translation.

5.2 An unbounded dependency does not match the GEPA constructor

The repository declares dspy>=3.0.0 without an upper bound, while the orchestration calls dspy.GEPA(metric=skill_fitness_metric, max_steps=iterations). In the latest DSPy snapshot fixed for this chapter, GEPA has no max_steps. It requires exactly one of auto, max_full_evals, or max_metric_calls, plus either a reflection_lm or a custom instruction_proposer. Compare the Hermes dependency, call site, and latest DSPy constructor.

# Hermes call site
dspy.GEPA(
    metric=skill_fitness_metric,
    max_steps=iterations,
)

# Relevant parameters in the latest DSPy snapshot
def __init__(
    metric,
    *,
    auto=None,
    max_full_evals=None,
    max_metric_calls=None,
    reflection_lm=None,
    instruction_proposer=None,
): ...

This is more than a renamed parameter. Exactly one budget option must be selected, and either a reflection model or proposer must be present. The current call supplies neither a valid budget nor the component that should read failures and propose new instructions.

Hermes also supplies a three-argument metric, while the latest DSPy constructor checks an interface with five values: gold, prediction, full trace, predictor name, and predictor trace. The configured optimizer_model is printed but never passed as GEPA's reflection LM. Under the latest dependency snapshot used here, the GEPA path therefore fails before reflection begins; the broad exception handler then switches execution to MIPROv2.

5.3 Structure validation receives a body but requires frontmatter

load_skill separates frontmatter from the body. The main flow passes skill["body"] and evolved_body into validate_all. Yet _check_skill_structure requires its input to begin with --- and contain name: and description: near the start. That condition necessarily fails for the current caller. See validate_all and _check_skill_structure.

# The caller supplies only the body.
validator.validate_all(skill["body"], "skill")
validator.validate_all(evolved_body, "skill", ...)

# The validator checks for a complete file.
has_frontmatter = text.strip().startswith("---")
has_name = "name:" in text[:500] if has_frontmatter else False
has_description = "description:" in text[:500] if has_frontmatter else False

Baseline and candidate therefore fail at the same check even when both bodies are valid. A repair must either validate the reassembled full skill or give body constraints and frontmatter constraints their own inputs, instead of asking one function to guess which layer it received.

5.4 A rich LLM judge exists, but optimization receives keyword overlap

fitness.py defines an LLMJudge with correctness, procedure following, conciseness, and textual feedback. That resembles the explanatory feedback GEPA can use. The function actually passed to dspy.GEPA(metric=...), however, computes word overlap between expected behavior and output and returns one float. Compare LLMJudge with skill_fitness_metric.

expected_words = set(expected.lower().split())
output_words = set(agent_output.lower().split())

overlap = len(expected_words & output_words) / len(expected_words)
score = 0.3 + (0.7 * overlap)
return min(1.0, max(0.0, score))

These lines are the objective the search actually sees. The proxy is fast, but it rewards repeating rubric vocabulary rather than necessarily completing the task. It also does not return the rich failure explanation through this metric path. That is an interface gap between the current search objective and the paper's reflective method, not a small matter of adjusting weights.

5.5 Tests, per-candidate gates, and PR delivery remain disconnected

ConstraintValidator contains run_test_suite, and configuration includes run_pytest and create_pr. The main flow does not call the test function and does not create a PR. Constraints run only after compilation on the final extracted body; they do not gate every candidate entering GEPA's pool. The success path writes files and metrics.json under output/. See run_test_suite and the output stage.

5.6 Broad fallback conflates unavailable GEPA with a failed GEPA run

The main flow catches any Exception from GEPA compilation and falls back to MIPROv2. That makes a demonstration more likely to continue, but it can relabel a real integration error as "GEPA not available." A rigorous run should fall back only for an explicit capability check and preserve all other failures.

This does not make the repository valueless. Session import, sample structure, skill wrapping, holdout comparison, and constraint classes form a useful base. The accurate status is narrower: a Phase 1 prototype exists, while end-to-end GEPA skill rewriting, hard gating, and reviewed delivery are not established by the current call path.

6. How to read “39.5% improvement”: ask what the experiment changed

The Phase 1 report describes one arxiv-skill experiment using MiniMax M2.5. Seven synthetic examples yielded three training and two validation examples. The optimizer was DSPy BootstrapFewShot and the metric was keyword overlap. Validation example one moved from 0.408 to 0.569, reported as +39.5%; example two stayed at 0.374. The average moved from 0.391 to 0.472, or +20.7%. Configuration and results appear in generate_report.py.

This was not a GEPA experiment, and it did not rewrite the skill. BootstrapFewShot selected strong execution traces as demonstrations and augmented the module. The supported conclusion is correspondingly narrow: on a tiny synthetic set and proxy metric, a DSPy-wrapped module scored higher after demonstration augmentation. It does not prove that GEPA is wired, or that a rewritten Hermes skill improves real tasks.

EvidenceWhat it testsWhat it cannot establish
GEPA paperPrompt evolution and ablations across six task familiesThe Hermes integration gets the same gains
Phase 1 reportOne skill, tiny synthetic set, BootstrapFewShot, keyword metricGEPA ran or rewrote the skill body
Current sourceData, orchestration, comparison, and constraint prototypesTests, PRs, and per-candidate gates form an automatic loop

The GEPA paper reports stronger evidence for the method itself: across six tasks, roughly 6% average and up to 20% improvement over GRPO, with up to 35 times fewer rollouts, plus more than 10% average improvement over MIPROv2. Those numbers make the method worth studying. Their evaluation target is the paper's benchmarks, not this Hermes prototype. Separating the layers makes the project's next work clearer.

7. What remains before the prototype becomes a credible loop

The path forward does not need more terminology. It needs every link in the evidence chain to carry through:

  1. Freeze the artifact. Record source commit, skill hash, model, and dataset configuration so the baseline can be replayed.
  2. Make the skill a real optimizable prompt parameter. Use DSPy's supported instruction or signature mechanism instead of relying on an ordinary attribute to mutate implicitly.
  3. Return the right objective. Prefer executable checks; use rubric judges for open tasks and pass actionable textual feedback into GEPA.
  4. Gate every candidate first. Validate full frontmatter, size growth, tests, and caching boundaries before a candidate enters the pool.
  5. Keep failures in the right layer. Do not let a different optimizer hide a failed GEPA integration; fallback should be an explicit run mode.
  6. Open holdout once. Report per-example outcomes, regressions on critical old tasks, and repeated runs or uncertainty, not only a percentage from one example.
  7. Account for the cost of improvement. A higher score cannot depend on unacceptable model cost, tokens, tool calls, or latency; report those budgets beside the baseline.
  8. Deliver a diff, not an overwrite. Package candidate, baseline, metrics, failures, and a PR for human approval.
  9. Wait for a future runtime boundary. A passed skill should refresh safely, not mutate the stable prompt prefix halfway through a conversation.
stable improvement on unseen tasks
+ no regression on critical old capabilities
+ cost and latency remain within budget
+ data, configuration, and candidate can be replayed
+ the old version can be restored
= eligible for a human delivery decision

Those steps close the offline search portion. Runtime use discovers experience worth preserving; offline evaluation treats a Skill as a versioned artifact and returns an approved revision for future sessions. New failures become evidence for the next cycle instead of immediate self-rewrites. The next chapter follows the remaining handoff: how Hermes gathers local verification evidence before a candidate reaches human review, and how it avoids ending a changed-code task without fresh passing evidence.

8. Conclusion: self-evolution strengthens evidence, not just text

Return to the opening question. Writing "run the generator first" into a skill does not establish an improvement. The revision earns that name only when old and new versions face the same independent tasks, the new one performs better on the right objective, and it preserves structure, tests, and runtime boundaries.

Runtime discovers experience.
Evaluation fixes the question.
Traces and feedback
explain failure.
Candidate pools
preserve different strengths.
Constraints and holdout
block self-persuasion.
Human review decides delivery.

Hermes Agent provides the first clock: how experience enters durable state after delivery. GEPA provides a method for the second: evolve prompt candidates through execution, reflection, and selection without updating model weights. Hermes Agent Self-Evolution puts the engineering interface between them on the table. Its current value is not that self-evolution is finished, but that the source makes the next gap inspectable.

Source and Paper References