gskill — A repository-specific skill for a coding agent¶
Skill-directory self-evolution. Given a repository and verifiable bug-fixing tasks, evolve one
SKILL.mdtree so a coding agent resolves more of them — and deploy the best tree unchanged. Runs through the sharedMethodPolicyrunner over aFileTreestrategy with theskill_dirstructural validator. Example:examples/gskill/gskill_repo_skill.py.
| Paper | none — a GEPA application: the blog post and guide (2026) |
| Upstream code | gepa-ai/gepa@f1cfdee, src/gepa/gskill |
| Example | examples/gskill/gskill_repo_skill.py |
| Domain | a vendored miniature repository (kvtools) with 12 injected bugs, 4/4/4 splits — not SWE-smith |
| Layer | L2 skill (blast_radius=0.6, set by the shared runner) |
| Fidelity | mechanism_microport — what the classes mean |
| Port author | chendanyang |
This is a mechanism microport, not a reproduction. It does not claim the blog's Jinja (55% → 82%) or Bleve (24% → 93%) numbers, nor its Claude Code transfer results: those need SWE-smith tasks, Docker and a real coding agent, none of which this port has.
The mechanism¶
gskill has three stages. SWE-smith injects bugs into a clean checkout of the
target repository, producing verifiable instances (problem statement,
FAIL_TO_PASS tests, PASS_TO_PASS tests). GEPA's optimize_anything then
evolves one text artifact — the skill, which starts empty: run the agent on a
batch, pass the problem, the agent's trace and the test output to a reflection
model ("the agent will not be evaluated on the same task"), and keep the proposal
if it scores better. The best skill is injected into the agent's prompt and
transfers to other agents without retraining.
Its fitness (swe_fitness_fn.py) is binary with a regression check: 1.0 only
if the FAIL_TO_PASS tests pass and the full suite still passes; no patch, an
f2p failure and a p2p regression each score 0 and are reported differently.
Where each piece lives¶
| Upstream mechanism | Where it lives here |
|---|---|
| SWE-smith instances | fixtures/bugs.json: a mutation over the clean fixtures/kvtools, a problem statement, FAIL_TO_PASS ids; PASS_TO_PASS is every other test |
| The fitness function | _fixtures.judge: binary, verdicts no_patch / f2p_failed / p2p_regression / all_passed |
| Mini-SWE-Agent rollout | solve: one model call that returns the repaired file; the skill is in its prompt |
| Reflection on traces + test output | propose: problem, attempt, verdict and test output → <EDITS> on the skill tree |
| Skills start empty | INITIAL_SKILL: a valid SKILL.md whose body says nothing yet |
Deploy best_skills |
export_skill / --export-skill DIR → DIR/.claude/skills/kvtools-fixer/ |
| Structurally valid skill | FileTree(validators=skill_tree_validator(...)) — see below |
| Train / val / test | gskill_splits(seed), stratified by the repository's conventions |
Structural rejection. The artifact is a directory, so a candidate can fail in
ways no rollout would show: delete SKILL.md, break its frontmatter, rename the
skill. The skill_dir validator rejects these before they are measured, and
FileTree counts them by reason (structural_rejections), so a reflector that
keeps producing unloadable skills is visible rather than silently wasted. The
exported tree must also pass validate_skill_tree or export_skill refuses it.
Stratified splits. kvtools has four house conventions — half-open ranges,
ConfigError for invalid values, partial pages count as one, a missing value is
None — none of them stated in code. Each convention has three bugs, one per
split. A skill that learned a convention from a train bug is therefore measured
on a different bug of the same convention in held-out and test: skills are
learned on some instances of a repository and judged on others, as in gskill.
Boundaries¶
- No SWE-smith and no Docker. Twelve vendored bugs in a ~60-line repository replace hundreds of mined instances. The verifier runs candidate code in a throw-away directory under a plain subprocess with a timeout and POSIX CPU/memory/file-size limits — containment against a runaway loop, not a sandbox for hostile code. Real gskill uses a container per rollout.
- The agent is one model call, not Mini-SWE-Agent with bash. It sees the
skill, the problem statement and the bugged file, and returns the whole
repaired file; it does not explore the repository. (Real runs can: point them
at SWE-smith and Docker with upstream's own
python -m gepa.gskill.train_optimize_anything.) - Edit protocol. Upstream's proposer rewrites one skill blob in a single code
fence; here proposals are the shared
<EDITS>protocol over a file tree, which is what lets the structural gate see a deleted or malformedSKILL.md. - No Pareto parent selection.
optimize_anythingsamples parents from the per-instance Pareto frontier. The shared population layer carries no per-instance scores yet, so the head is mutated under a SkillOpt-style strict held-out gate (StrictHeldOutGate, ~15 lines at theacceptanceseam): commit only a strictly higher held-out pass rate. With a binary fitness on a handful of tasks, a statistical gate would mostly measure noise. - One failed task per proposal, like upstream's
--proposer loopitem calls; its follow-up merge call is not ported.reflective=False, because a model-synthesised merge reaches the ledger without passingto_diffand would bypass the frontmatter validator. - Out of scope, as in the issue: shipping SWE-smith, evolving executable skill scripts (gskill's "what's next"), and the plugin installers.
Measured results — kvtools fixture¶
No live run is recorded. The offline tests drive the real engine in every scheduler with a scripted model that applies a convention only when the skill states it; that checks the loop (propose → validate → gate → export), not the quality of any model, so no number from it belongs in the results table. The row will be added when a live run exists.
Run it¶
python -m examples.gskill --dry-run # no network, no Docker, no API key
# live: any OpenAI-compatible endpoint or Claude; write the best skill for a host
python -m examples.gskill --yes --provider openai --model deepseek-v4-flash \
--workers 4 --candidates 8 --max-tokens 4096 \
--export-skill ./out
# -> ./out/.claude/skills/kvtools-fixer/SKILL.md
--max-tokens 4096 because a proposal carries the whole skill file inside JSON.
--export-skill writes the skill the run ended on, after checking it is a valid
Agent Skills tree.
Flags: the MethodPolicy command line.
Offline tests: tests/test_gskill.py, tests/test_candidate_methods.py.