Build1 publisher3 min readPublished
oh-my-agent admits a promoted fixture only when the failing run's output still fails it
The 14.13.1 release captures failed agent runs as incidents, promotes them into skill fixtures, and accepts an edit only when one split improves and neither regresses, all under an enforced dispatch budget.
The Engineer · Build desk

What happened
- The oh-my-agent CLI moved from 14.7.11 to 14.13.1 across 131 commits, with the release built around turning failed agent runs into skill regression fixtures.
- The command `oma skill optimize` accepts a skill edit only when neither the training nor the validation split regresses and at least one of the two improves.
- The ceiling in `constitution.budget.max_dispatches_per_run` is now enforced: the call that would exceed it is refused, and promotion is blocked.
- A judge parsing PASS/FAIL straight from the Claude envelope read every verdict as FAIL because the field "failed":0 came first, and outputs are now unwrapped before scoring.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- exposure Any skill score this harness recorded before the unwrap fix is unusable, and the release discards stale recordings, so a team that tuned skills against those numbers has nothing to compare its next run against.
- constraint The spend ceiling for a tuning run is set in config, and an over-budget dispatch surfaces as a blocked promotion, so the loop cannot quietly buy a better score with more model calls.
- capability A skill that never gets picked is now separable from a skill whose body does not help, because the routing eval shows the model every installed description and records whether it chooses the target, a neighbor, or nothing.
The oma-debug fixture set is the part other projects should copy. Its first version scored 89% with no skill loaded [19]. That leaves eleven points between the bare model and a perfect score, so no edit to the skill body could have moved the number by more than eleven [1]. The replacement twelve score 25% at baseline and 100% with the skill [20], which is 75 points of room [2]. oma-refactor got the same rewrite and went from 16.7% to 100% [21], 83.3 points [3].
The figures come from the project's own fixtures, written for its own skills, and scored by a judge that read FAIL on every run until this release [14]. For the 25-to-100 gap to mean anything in another repo, the fixtures have to be built the same way: the model has to fail them with the skill uninstalled. A skill that documents behaviour the model already has will reproduce the 89% table.
The capture path is three commands. `oma harness incident scan` lists failed, blocked and partial runs that no incident references [2]. `oma harness incident promote` derives a fixture for the skill the agent used, and admits it only when the recorded failing output fails it [3]. That is red-first, enforced by the tool. Validation runs against the last 64 KiB of runner log, which is what a finished run keeps [5]; output from earlier in a long run is not there to check against [8].
The first loop-verified edit landed in oma-docs, where the held-in score went from 55.6% to 77.8% and held-out validation stayed at 100% [23]. Both held-in figures are exact ninths, 5/9 and 7/9, which fits nine training fixtures and three held out of that skill's twelve [4]. The held-out split was already at ceiling, so the acceptance rule was asking it not to regress and asking the other split for the 22.2 points [4].
The judge failure and the broken weekly cross-post job share a cause. `claude --output-format json` wraps the answer in an envelope, and `runAgent` returned it raw [15]. The judge read PASS/FAIL out of that envelope, where `"failed":0` appears before the verdict [14]. While that held, no skill edit could be accepted at all: acceptance needs at least one split to improve [6], and a scorer that fails everything reports no improvement anywhere [7].
Meta-optimization applies the same shape to the optimizer prompt, with promotion gated on a seeded paired bootstrap 95% interval above zero and at least three pairs [9]. The first adopted procedure beat the incumbent by 0.27 on average, interval [0.04, 0.54], and added three grounding rules for proposed skill edits [10]. The post does not say what scale that score is on. Three pairs is three differences, and the lower bound is 0.04.
One epoch with four candidates used to take forty minutes because every arm and judge ran serially [16]. Dispatches now go through a bounded pool with a default of four [17], which puts four equal-length arms near ten minutes [5]. The per-dispatch timeout is 180 seconds with one retry [18], so a single hung dispatch can hold a slot for six minutes [6].
Comparable runs need an identical tool surface. A PreToolUse hook now denies native Grep and Glob, plus `rg`, `grep -r`, `find -name` and `git grep`, whenever a code-intelligence provider is configured, and the deny message names the Serena tool to use instead [12]. The escape hatch is `OMA_CI_ALLOW_NATIVE=1`, named for the one place where comparability matters most [13]. The guard goes away entirely with `providers.code_intelligence_guard: off` [13].
What to watch
- Whether the meta-optimize gate holds as pair counts rise past three; a 0.27 mean with a 0.04 lower bound is the weakest adoption the rule permits.
- Whether oma-docs, oma-scm and oma-qa publish no-skill baselines for their twelve fixtures the way oma-debug and oma-refactor did.
- Whether 64 KiB of retained runner log proves enough to validate fixtures from long runs, or the retention window grows.