Repository navigation
fix(jobs): ask again when a redline change's new words hold model formatting - #2434
Merged
Merged
Conversation
…matting On Claude Haiku 5.5 a redline reply sometimes runs on past the end of new_text, and what it writes there went into the contract as tracked text: ".},", ".`,", ".](#)", "</parameter>", or a whole "','external_comment':''}," tail. On the dev set (pack 0.1.0, three runs) 12 of 96 changes ended so, and Sonnet 5.5 left a "}" in two test-set changes. decide.py now checks a change's new words (markup.py) and, like a quote or a placement that fails, asks again once, showing the model the words that hold the mark; still there, the point goes to a person as MARKUP. A mark counts only when no paragraph of the contract writes one of its kind: a tag, a Markdown link, an elision, a brace, a backtick, or a closing bracket that closes nothing, beyond those the replaced words close. So "[__]" blanks, "(i)" items, defined terms in brackets and a contract's own "a)" lists or braces are kept.
The checks step names the marks a change's new words may not hold and what Haiku 5.5 wrote on the dev cases before and after the check. A Known gap records that comments are not checked so: 1 of 1,350 eval comments ended with a Sonnet 5.5 "}".
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
|
Caution Review failedThe pull request is closed.
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
The contract redline no longer writes a model's formatting into a contract. When a change's new words hold a mark the contract never uses, the model is asked once more and shown the words that hold the mark. If the mark is still there, the point goes to a person as
MARKUPand that part of the contract is left as it is.modules/jobs/word_clauses/markup.pyruns indecide.pybeside the quote and placement checks, on the new words of a replace or an insert. It refuses:</parameter>,</br>);](#),](...));...);{Company}placeholders ora)lists can still be written.[__]blanks,(i)items and defined terms in brackets are never refused. On the 6 dev NDAs, every paragraph of each contract, checked as new text in each of the other five (955 checks), passes.repair.mdas$problem, so pack 0.1.0 is unchanged:Your new_text is not only contract words: it holds a brace, "Agreement.},". Give new_text as the words the contract will read and nothing else: no Markdown, tags, code or JSON.docs/architecture/jobs.mddescribes the new check under the redline's checks. A new Known gaps line says comments are not checked this way: across all eval runs, 1 of 1,350 comments held a mark, a}from Sonnet 5.5.Why
Sometimes Haiku 5.5's reply runs on past the end of
new_text, and the runner wrote whatever followed into the contract as tracked text. That text reached the "With changes" download. While the redline guide was being captured, both Haiku runs on dev-406 did this (...result from them.](...),...so retained.</parameter>,...this Agreement.},twice). On the 6 Nov test-set run, Sonnet 5.5 did it twice (}).The eval below used the dev set: 6 ContractNLI NDAs, with the Bonterms Mutual NDA as the playbook. It ran pack 0.1.0 on Haiku 5.5 through OpenRouter (
openrouter:anthropic/claude-haiku-5.5@anthropic), 3 runs per arm, 288 scored points each:dev(before)])MARKUPCHANGES_OVERLAP,NEEDS_REVIEW)The 12 insertions in the before arm ended with:
.},(4 times);.](...)and.](#);."},;','','']},;’,’external_comment’:’’ ,’_’:’’},;)(years.) ,).How markup was counted. Every tracked insertion in each redlined copy was scanned for tags,
](, braces, backticks,**,..., escapes and trailing brackets. That list is wider than the check's, and each flag was read by hand. After the change, the only flags are 6 trailing)in dev-411. Each closes a parenthesis its own clause opens, so it is contract text. No paragraph the after arm changed was left with a bracket that closes nothing.Outcomes. The before arm's own three runs ranged from 77 to 81 of 96 outcomes right; each after run scored 81. So this PR claims no loss, not a gain. The gold is still a draft: the maintainer has not yet checked
firm/stance.json.Part of #2414. No issue was filed for this bug.
How to test
cd surfsense_local/backend PYTHON_DOTENV_DISABLED=1 uv run pytest tests/unit/jobs/test_word_clauses.pyThe new tests in
tests/unit/jobs/test_word_clauses.pyall go throughreview_contract:test_new_text_holding_the_models_formatting_is_asked_again[...]covers the shapes Haiku and Sonnet wrote (.](...),.</parameter>,.},, a backtick and a comma,','external_comment':''},, a stray)) and an elision. The model is asked again with the words that hold the mark, and the clean second change is made.test_formatting_the_model_writes_again_goes_to_a_person: when the mark is still there, the code isMARKUP, nothing is inserted, and the person's note says why.test_a_closing_bracket_the_replaced_words_hold_is_kept: anold_textending in)is replaced by new words ending in).test_brackets_a_contract_writes_in_new_text_are_kept[...]:[__]blanks,(each, an “Oral Disclosure”),(i)and(ii).test_marks_the_contract_itself_writes_are_kept_in_new_text: a contract witha)lists and{Company}.On
dev'sdecide.py, the 7 leak cases and the person test fail. The tests that check marks are kept pass there, since they guard against false refusals.Each guard was checked by removing it. Without either half of the rule about the contract's own marks,
test_marks_the_contract_itself_writes_are_kept_in_new_textfails. Without the comparison with the replaced words,test_a_closing_bracket_the_replaced_words_hold_is_keptfails.The eval runs from
surfsense_local/backendagainst the privatesurfsense-jobsrepo. The results are incontract-redline/eval/results/2026-11-09-dev-haiku-before-markup-checkand2026-11-09-dev-haiku-markup-check, not committed. The after arm ran through a wrapper that only logged each check, which is where the count of 7 comes from. Each arm ran:The test set was not run, since it runs once per frozen pack.
High-level PR Summary
This PR prevents model-generated formatting artifacts from being written into contract documents during the redline process. When a language model's response overruns its intended string boundary, it sometimes includes JSON syntax, Markdown links, or HTML tags in the tracked text. The fix adds a validation check that detects these formatting marks in proposed changes, asks the model again with the problematic words highlighted, and if the marks persist, escalates the point to human review with a
MARKUPcode. The check is intelligent about contract-native formatting, only rejecting marks the contract itself never uses, so legitimate elements like placeholder braces{Company}, list markersa), and bracketed items(i)are preserved. Evaluation on 6 NDA contracts showed this eliminated all 12 instances of leaked formatting (braces, links, tags) that appeared in the baseline, with only 7 retry requests needed and no changes incorrectly flagged.⏱️ Estimated Review Time: 5-15 minutes
💡 Review Order Suggestion
surfsense_local/backend/modules/jobs/word_clauses/markup.pysurfsense_local/backend/modules/jobs/word_clauses/decide.pysurfsense_local/backend/tests/unit/jobs/test_word_clauses.pydocs/architecture/jobs.md