Skip to content

v0.18.0: remember rejected texts, not just rejected labels

Latest

Choose a tag to compare

@madetocreate madetocreate released this 04 Sep 22:17

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 all

Also in this release

  • evolution.rejectionNoteLimit / --rejection-notes <n>: how many reasons are
    quoted (default 5, 0 for none). The refusal has no flag: it is a correctness
    property, not a preference.
  • A cool-down of minRuns runs 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_repeat and rejection_forgotten, plus two new
    evolution_skipped reasons.
  • darwin status shows the memory and the cool-down; darwin approve lists
    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