Skip to content

feat: add --score-command metric gate with keep/discard and non-improving stop - #186

Open
caretak3r wants to merge 1 commit into
kunchenguid:mainfrom
caretak3r:feat/score-command-metric-gate
Open

feat: add --score-command metric gate with keep/discard and non-improving stop#186
caretak3r wants to merge 1 commit into
kunchenguid:mainfrom
caretak3r:feat/score-command-metric-gate

Conversation

@caretak3r

Copy link
Copy Markdown

Motivation

gnhf currently commits every iteration the agent reports as successful. The agent's self-report is the only gate, so a run can accumulate commits that make the actual target worse. This PR adds an optional, externally measured keep/discard gate: score the workspace after each successful iteration and keep the commit only if the score strictly improved.

What it does

New flags (all optional; default behavior is unchanged when --score-command is unset):

  • --score-command <cmd>: shell command run in the workspace after a successful iteration; its last non-empty stdout line is parsed as a number.
  • --score-direction <min|max>: required with --score-command; defines improvement.
  • --stop-after-non-improving <N>: stop the run after N consecutive non-improving iterations (default 3), with a distinct stop reason.

Behavior:

  • Strict improvement: commit as usual, then persist the best score to .gnhf/runs/<id>/best-metric (only after the commit lands, so a commit failure cannot advance the best). Resume reads it back, following the stop-when file pattern.
  • Worse, equal, command failure, or unparseable output: treated as a failed iteration; the existing recordFailure/resetHard path discards the changes. Never crashes the run.
  • Debug events: metric:gate, metric:gate:accepted, metric:gate:rejected, metric:score:command-failed, metric:score:parse-failed.

Tests

  • 3 new e2e tests (e2e/e2e-metric-gate.test.ts) against dist/cli.mjs + the mock server: improving scores commit and persist best-metric; non-improving scores leave no commit and a clean worktree; unparseable scores stop the run via the metric gate without crashing.
  • Full local run: lint, format:check, typecheck clean; 194/194 tests pass on macOS.

Naming and UX are negotiable: happy to rename the flags (e.g. --verify-command) or feed the measured score back into iteration prompts if you'd prefer that direction.

…ving stop

Score each successful iteration with an external command before commit.
A score strictly better than best (per --score-direction min|max) commits
and persists best-metric under .gnhf/runs/<id>/ (resume-safe). A worse or
unparseable score resets the worktree via the existing failure path and
never crashes the run. The run stops after --stop-after-non-improving
consecutive non-improving iterations (default 3) with a distinct stop
reason. Default behavior is unchanged when --score-command is unset.
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