Skip to content

Novelty judge can never reject: prompt asks for NOT_NOVEL, parser matches "NOT NOVEL" #483

Description

@generalmark763-del

Summary

ProgramDatabase._llm_judge_novelty can never return a rejection when the LLM follows the project's own prompt. The novelty prompt asks the model to answer NOT_NOVEL (underscore), but the parser searches for NOT NOVEL (space), so every rejection is parsed as an acceptance and the novelty check admits everything.

Where

openevolve/novelty_judge.py (lines 26 and 43 on main) instructs the model:

- **NOT_NOVEL**: If the codes are essentially the same with only trivial differences
...
Are these codes meaningfully different? Respond with NOVEL or NOT_NOVEL followed by your explanation.

openevolve/database.py (lines 1048-1049 on main) parses the reply:

NOVEL_i = content.upper().find("NOVEL")
NOT_NOVEL_i = content.upper().find("NOT NOVEL")

On "NOT_NOVEL", find("NOVEL") matches the substring at index 4 while find("NOT NOVEL") returns -1. Control therefore reaches the elif NOVEL_i != -1: branch and is_novel = True.

Reproduction

No install required — this mirrors the parsing block at database.py:1048-1061:

def parse(content):
    c = content.upper()
    novel_i, not_novel_i = c.find("NOVEL"), c.find("NOT NOVEL")
    if novel_i == -1 and not_novel_i == -1:
        return True
    if novel_i != -1 and not_novel_i != -1:
        return novel_i < not_novel_i
    return novel_i != -1

for r in ["NOT_NOVEL",
          "NOT_NOVEL: the codes are essentially the same",
          "**NOT_NOVEL**",
          "NOT NOVEL"]:
    print(f"{r!r:50} -> is_novel={parse(r)}")
'NOT_NOVEL'                                        -> is_novel=True    # expected False
'NOT_NOVEL: the codes are essentially the same'    -> is_novel=True    # expected False
'**NOT_NOVEL**'                                    -> is_novel=True    # expected False
'NOT NOVEL'                                        -> is_novel=False   # correct, but never prompted for

Only the space-separated form parses correctly, and the prompt never asks for it.

Impact

LLM-based novelty deduplication is inert for any user on the default prompt, independent of similarity_threshold or model choice. Programs the judge identifies as trivially different are admitted anyway.

Measured over 100 independent code-pair cases sent through the judge path with a current-generation model: 0 rejections. Re-parsing the same LLM responses with the fix below, the judge rejects 20/20 provably behaviour-preserving edits (identical AST after comment and whitespace normalisation) while still admitting the majority of behaviour-changing ones.

Suggested fix

Normalise the underscore form before parsing. Acceptances and the already-correct space form are unaffected:

             content = content.strip()

             # Parse the response
-            NOVEL_i = content.upper().find("NOVEL")
-            NOT_NOVEL_i = content.upper().find("NOT NOVEL")
+            # novelty_judge.py prompts for "NOT_NOVEL"; normalise so a rejection is
+            # not read as an acceptance.
+            _parsed = content.upper().replace("NOT_NOVEL", "NOT NOVEL")
+            NOVEL_i = _parsed.find("NOVEL")
+            NOT_NOVEL_i = _parsed.find("NOT NOVEL")

A token-based parse rather than substring matching would be a more thorough alternative, also covering variants like NOT-NOVEL. Happy to open a PR with either shape plus a regression test if you have a preference.

Environment

Found at 411fb59; verified still present on main on 2026-08-10. Discovered during adversarial code review while evaluating novelty-filtering behaviour.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions