Skip to content

feat: add GEPA code-state CLI mutator#13

Open
twaldin wants to merge 1 commit into
mainfrom
feat/gepa-code-mutator
Open

feat: add GEPA code-state CLI mutator#13
twaldin wants to merge 1 commit into
mainfrom
feat/gepa-code-mutator

Conversation

@twaldin

@twaldin twaldin commented May 20, 2026

Copy link
Copy Markdown
Owner

Summary

  • add a first-class GEPA custom proposer path that uses harness CLIs to mutate code state in worktrees
  • wire optimize_code to keep GEPA in charge of selection/frontier/budget while Hone commits CLI-produced code candidates
  • add hone optimize-code CLI surface and tests for proposal, HEAD/worktree behavior, and CLI wiring

Test Plan

  • PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 python3 -m pytest -q
  • python3 -m ruff check src/hone/gepa_faithful.py src/hone/cli.py tests/test_gepa_faithful.py
  • git diff --check

Note: plain python3 -m pytest -q currently fails before collection because a globally auto-loaded pytest_httpbin plugin imports missing flask; project tests pass with plugin autoload disabled.

Summary by CodeRabbit

Release Notes

  • New Features
    • Added optimize-code CLI command for automated code optimization across git repositories using GEPA framework.
    • Extended GEPA optimizer to dynamically generate and propose code changes during optimization iterations via custom proposal generation.

Review Change Stack

@coderabbitai

coderabbitai Bot commented May 20, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR integrates a custom candidate proposer mechanism into GEPA optimization, enabling HarnessMutatorAdapter to generate new code candidates by extracting context from GEPA's reflective dataset. The feature is wired through optimize_code, exposed via a new CLI command, and tested end-to-end.

Changes

Custom Candidate Proposer Integration for GEPA Optimization

Layer / File(s) Summary
Proposer Hook Architecture
src/hone/gepa_faithful.py
HarnessMutatorAdapter.propose_new_texts derives candidate instructions from reflective dataset context, applies mutations, and returns a commit-tied candidate record. New _ProposerAdapter bridges the proposer to GEPA. Helper functions attach proposer config and extract instruction context with fallback logic.
Runner Integration
src/hone/gepa_faithful.py
GepaTsOptimizeAnythingRunner.optimize_anything accepts optional custom_candidate_proposer parameter, attaches it to GEPA config, and passes _ProposerAdapter wrapper into the GEPA call.
optimize_code API Extension
src/hone/gepa_faithful.py
optimize_code accepts mutator_adapter parameter, initializes proposer and proposal counter, creates gepa_custom_candidate_proposer closure, and wires it into the runner's optimize_anything call.
CLI Command for optimize-code
src/hone/cli.py
Imports GEPA types, parses Typer arguments for repo, test command, seeding, objectives, and harness configuration, constructs OptimizeCodeConfig and HarnessMutatorAdapter, invokes optimize_code, and renders results in Rich panel.
Tests for Proposer and CLI
tests/test_gepa_faithful.py
Verifies propose_new_texts commits code state and creates workdir, confirms optimize_code wires proposer into runner with correct context, and validates optimize-code CLI forwards parameters and outputs expected GEPA metadata.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

A harness learns to whisper back,
Proposing code through reflection's track,
GEPA listens, mutates with care,
CLI commands float through the air! 🐰✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: add GEPA code-state CLI mutator' clearly and specifically describes the primary change: introducing a CLI command for GEPA-based code-state mutation.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/gepa-code-mutator

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/hone/cli.py`:
- Around line 336-346: Wrap the call to optimize_code in a try/except that
catches Exception; on exception, use typer.secho to print a red error message
including the exception message (and optionally repr) and then raise
typer.Exit(code=2) to terminate cleanly; update the block where
optimize_code(...) is called (referencing the optimize_code call and its
parameters like repo, test_command, seed_instructions, base_commit,
max_metric_calls, objective, background, config, mutator_adapter) so that
failures produce the red message + typer.Exit rather than letting the traceback
bubble.

In `@src/hone/gepa_faithful.py`:
- Around line 295-316: The proposer currently builds new instructions via
_instructions_from_reflection and calls mutate but then returns a GEPA candidate
that still contains the old code_candidate.instructions and drops
components_to_update; update the returned candidate to include the new/returned
instructions and preserve any components_to_update from the mutated candidate.
Concretely, after calling mutate(HoneCodeCandidate(...)), construct the value
passed to candidate_to_gepa using the commit_sha from
mutation.candidate.commit_sha and instructions from
mutation.candidate.instructions (or fall back to the local instructions
variable), and also copy mutation.candidate.components_to_update (or
code_candidate.components_to_update if appropriate) into the returned
HoneCodeCandidate so the proposed text change is actually applied.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b8771ee6-e125-4990-994c-b37f1d3bc085

📥 Commits

Reviewing files that changed from the base of the PR and between 69d88d5 and 5850e49.

📒 Files selected for processing (3)
  • src/hone/cli.py
  • src/hone/gepa_faithful.py
  • tests/test_gepa_faithful.py

Comment thread src/hone/cli.py
Comment on lines +336 to +346
result = optimize_code(
repo_path=repo,
test_command=test_command,
seed_instructions=seed_instructions,
base_commit=base_commit,
max_metric_calls=max_metric_calls,
objective=objective,
background=background,
config=config,
mutator_adapter=adapter,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Handle optimize-code failures with CLI-friendly exits.

This path currently lets exceptions bubble as tracebacks. Converting to a red message + typer.Exit(code=2) keeps behavior consistent with other commands and improves automation UX.

💡 Suggested fix
-    result = optimize_code(
-        repo_path=repo,
-        test_command=test_command,
-        seed_instructions=seed_instructions,
-        base_commit=base_commit,
-        max_metric_calls=max_metric_calls,
-        objective=objective,
-        background=background,
-        config=config,
-        mutator_adapter=adapter,
-    )
+    try:
+        result = optimize_code(
+            repo_path=repo,
+            test_command=test_command,
+            seed_instructions=seed_instructions,
+            base_commit=base_commit,
+            max_metric_calls=max_metric_calls,
+            objective=objective,
+            background=background,
+            config=config,
+            mutator_adapter=adapter,
+        )
+    except Exception as exc:
+        console.print(f"[red]optimize-code failed: {exc}[/red]")
+        raise typer.Exit(code=2) from exc
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
result = optimize_code(
repo_path=repo,
test_command=test_command,
seed_instructions=seed_instructions,
base_commit=base_commit,
max_metric_calls=max_metric_calls,
objective=objective,
background=background,
config=config,
mutator_adapter=adapter,
)
try:
result = optimize_code(
repo_path=repo,
test_command=test_command,
seed_instructions=seed_instructions,
base_commit=base_commit,
max_metric_calls=max_metric_calls,
objective=objective,
background=background,
config=config,
mutator_adapter=adapter,
)
except Exception as exc:
console.print(f"[red]optimize-code failed: {exc}[/red]")
raise typer.Exit(code=2) from exc
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hone/cli.py` around lines 336 - 346, Wrap the call to optimize_code in a
try/except that catches Exception; on exception, use typer.secho to print a red
error message including the exception message (and optionally repr) and then
raise typer.Exit(code=2) to terminate cleanly; update the block where
optimize_code(...) is called (referencing the optimize_code call and its
parameters like repo, test_command, seed_instructions, base_commit,
max_metric_calls, objective, background, config, mutator_adapter) so that
failures produce the red message + typer.Exit rather than letting the traceback
bubble.

Comment thread src/hone/gepa_faithful.py
Comment on lines +295 to +316
del components_to_update
code_candidate = (
candidate_from_gepa(candidate) if isinstance(candidate, Mapping) else candidate
)
instructions = _instructions_from_reflection(
fallback=code_candidate.instructions,
reflective_dataset=reflective_dataset,
)
mutation = self.mutate(
HoneCodeCandidate(
commit_sha=code_candidate.commit_sha,
instructions=instructions,
),
example,
iteration=iteration,
)
return candidate_to_gepa(
HoneCodeCandidate(
commit_sha=mutation.candidate.commit_sha,
instructions=code_candidate.instructions,
)
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Return updated instruction text from proposer output.

propose_new_texts builds reflection-derived instructions but returns the old candidate.instructions and ignores components_to_update. That can turn text proposals into a no-op even though a new code commit is produced.

💡 Suggested fix
-        del components_to_update
         code_candidate = (
             candidate_from_gepa(candidate) if isinstance(candidate, Mapping) else candidate
         )
         instructions = _instructions_from_reflection(
             fallback=code_candidate.instructions,
             reflective_dataset=reflective_dataset,
         )
@@
-        return candidate_to_gepa(
-            HoneCodeCandidate(
-                commit_sha=mutation.candidate.commit_sha,
-                instructions=code_candidate.instructions,
-            )
-        )
+        updated_instructions = (
+            instructions
+            if "instructions" in set(components_to_update)
+            else code_candidate.instructions
+        )
+        return candidate_to_gepa(
+            HoneCodeCandidate(
+                commit_sha=mutation.candidate.commit_sha,
+                instructions=updated_instructions,
+            )
+        )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
del components_to_update
code_candidate = (
candidate_from_gepa(candidate) if isinstance(candidate, Mapping) else candidate
)
instructions = _instructions_from_reflection(
fallback=code_candidate.instructions,
reflective_dataset=reflective_dataset,
)
mutation = self.mutate(
HoneCodeCandidate(
commit_sha=code_candidate.commit_sha,
instructions=instructions,
),
example,
iteration=iteration,
)
return candidate_to_gepa(
HoneCodeCandidate(
commit_sha=mutation.candidate.commit_sha,
instructions=code_candidate.instructions,
)
)
code_candidate = (
candidate_from_gepa(candidate) if isinstance(candidate, Mapping) else candidate
)
instructions = _instructions_from_reflection(
fallback=code_candidate.instructions,
reflective_dataset=reflective_dataset,
)
mutation = self.mutate(
HoneCodeCandidate(
commit_sha=code_candidate.commit_sha,
instructions=instructions,
),
example,
iteration=iteration,
)
updated_instructions = (
instructions
if "instructions" in set(components_to_update)
else code_candidate.instructions
)
return candidate_to_gepa(
HoneCodeCandidate(
commit_sha=mutation.candidate.commit_sha,
instructions=updated_instructions,
)
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/hone/gepa_faithful.py` around lines 295 - 316, The proposer currently
builds new instructions via _instructions_from_reflection and calls mutate but
then returns a GEPA candidate that still contains the old
code_candidate.instructions and drops components_to_update; update the returned
candidate to include the new/returned instructions and preserve any
components_to_update from the mutated candidate. Concretely, after calling
mutate(HoneCodeCandidate(...)), construct the value passed to candidate_to_gepa
using the commit_sha from mutation.candidate.commit_sha and instructions from
mutation.candidate.instructions (or fall back to the local instructions
variable), and also copy mutation.candidate.components_to_update (or
code_candidate.components_to_update if appropriate) into the returned
HoneCodeCandidate so the proposed text change is actually applied.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant