v0.17 shipped the human approval gate and, in the same README, named what it did
not do: "useDemos plus the gate can re-propose the same challenger. Darwin
remembers rejected version LABELS, not rejected TEXTS; giving it the second kind
of memory is a separate piece of work, not a line in this one."
This is that piece of work.
The same text is not proposed twice
Rejecting a challenger fingerprints its prompt (SHA-256 over the text,
whitespace normalised) and stores it against the agent. When a generator
produces that text again, it is not proposed.
Two properties matter more than the fingerprint:
A repeat falls through to the next generator rather than ending the cycle.
The demo path is the deterministic one, so it is the one that repeats; GEPA and
the legacy optimizer are not. Only a cycle in which every generator repeats is
refused, and that message names the way out. A memory that quietly froze an
agent would be worse than the problem it solves.
The refusal holds with the approval gate off. Rejections only ever come from
the gate, but the gate can be turned off afterwards, and putting an explicitly
rejected text onto half of live traffic is worse than asking again.
The reviewer's reason reaches the next generation
--reason was recorded for the human up to v0.17. It is now quoted back to
whichever optimizer runs next: in the legacy meta-prompt as its own block
immediately before the task line, and for the GEPA reflector as its own feedback
entry, labelled with the rejected version.
darwin approve writer --reject --reason "drops the citation rule"
darwin approve writer --forget v4 # let that text be proposed again
darwin approve writer --forget allAlso in this release
evolution.rejectionNoteLimit/--rejection-notes <n>: how many reasons are
quoted (default 5,0for none). The refusal has no flag: it is a correctness
property, not a preference.- A cool-down of
minRunsruns after a refused cycle, so a stuck agent stops
paying for an answer already known. Ignored by--force, and not re-armed by
it either. - Metric events
rejected_repeatandrejection_forgotten, plus two new
evolution_skippedreasons. darwin statusshows the memory and the cool-down;darwin approvelists
what was already turned down above the diff.
Said plainly, because it matters
This is exact-match memory, not semantic memory. Whitespace is normalised;
nothing else is. A challenger that differs by one word is a NEW proposal and
will be shown to you. A fuzzy match would eventually refuse something a reviewer
wanted to see, and a refusal nobody can predict is worse than one question too
many.
--reason text reaches the model as an instruction. Treat it like the prompt
itself and do not pipe untrusted text into it.
Verify
941 tests (940 pass, 1 pre-existing skip), on Node 20 and Node 22, both under CI
conditions. tsc, typecheck:tests, build, adapter-compat, the coverage gate and
benchmark --dry green. 27 mutation probes, each one red before its fix.
Two adversarial review rounds, both NO-GO, sixteen confirmed findings between
them. The heaviest finding of round 2 sat inside what round 1 had repaired: a
guard reading slice(indexOf(a), indexOf(b)) is empty whenever a comes after b,
so it passed with the block moved to the one place the README says it must not
be. Ordering cannot pin a placement; a slice cannot pin a direction. Both
assertions now stand together.
Full notes: CHANGELOG.md