Avoid assistant prose for forced edit retry
ober
236e9b7033a6c6a75f7fa3b61548089c0522bda9
--- a/src/jcode/core/verified-run.ss +++ b/src/jcode/core/verified-run.ss @@ -398,8 +398,7 @@ '("edit" "write")))) #t) (else (loop (cdr xs)))))) - (make-text-response - "Forced first-edit turn rejected: the model called a hidden inspection or non-edit tool. Do not call read, list, balance, verify, or any other tool on this turn. Call exactly edit(path=<scoped target file>, content=<complete minimal acceptance-complete file body>) or write(path=<scoped target file>, content=<complete minimal acceptance-complete file body>) now.") + (make-text-response "JCODE_INTERNAL_FORCE_FIRST_EDIT_RETRY") response)) (def (specified-tool-choice-supported? provider) --- a/src/jcode/core/workflow-runner.ss +++ b/src/jcode/core/workflow-runner.ss @@ -360,6 +360,22 @@ (message-content (car rev))) (else (loop (cdr rev)))))) +(def forced-first-edit-retry-marker + "JCODE_INTERNAL_FORCE_FIRST_EDIT_RETRY") + +(def (forced-first-edit-retry? content) + (and (string? content) + (string-prefix? forced-first-edit-retry-marker content))) + +(def (forced-first-edit-retry-message) + (string-append + "VERIFIED WORKFLOW REQUIRES A STRUCTURED TOOL CALL NOW. " + "The previous turn attempted a hidden inspection while only edit/write " + "were available. Do not explain, summarize, ask questions, read, list, " + "balance, verify, or call any other tool. Next response must be exactly " + "one structured edit(path=<scoped target file>, content=<complete file>) " + "or write(path=<scoped target file>, content=<complete file>) tool call.")) + (def (verified-text-retry-message workflow enforcer messages raw retry-count) (let* ((completed (step-enforcer-completed enforcer)) (pending (step-enforcer-pending enforcer)) @@ -521,8 +537,11 @@ (cond ;; Intentional text response — emit and consume an iteration. ((text-response? response) - (let ((content (text-response-content response))) - (emit! (make-assistant-message content)) + (let* ((content (text-response-content response)) + (forced-first-edit-retry + (forced-first-edit-retry? content))) + (unless forced-first-edit-retry + (emit! (make-assistant-message content))) (let ((tool-calls (and text-response-tool-calls (text-response-tool-calls content)))) @@ -543,10 +562,12 @@ "Model failed to produce a valid tool call after text-response retries." content)) (emit! (make-user-message - (text-retry-message + (if forced-first-edit-retry + (forced-first-edit-retry-message) + (text-retry-message workflow enforcer messages content (error-tracker-consecutive-retries - error-tracker)))) + error-tracker))))) (loop (+ iteration 1))) (loop (+ iteration 1))))))) (else --- a/test/run.ss +++ b/test/run.ss @@ -5865,6 +5865,7 @@ [readme-path (string-append vr-dir "/" readme)] [forced-spec-names '()] [tool-results '()] + [seen-messages '()] [calls 0] [read-call (lambda (line) @@ -5906,6 +5907,7 @@ (cons 'max-iterations 20) (cons 'on-message (lambda (m) + (set! seen-messages (cons m seen-messages)) (when (equal? (message-role m) "tool") (set! tool-results (cons (message-content m) tool-results)))))))]) @@ -5925,7 +5927,32 @@ (check! "verified-run: hidden remote read was not executed" (length (filter (lambda (s) (str-contains? s "context-")) (reverse tool-results))) - 10)) + 10) + (check! "verified-run: forced first-edit marker is not assistant prose" + (let loop ([xs seen-messages]) + (cond + [(null? xs) #f] + [(and (equal? (message-role (car xs)) "assistant") + (str-contains? (message-content (car xs)) + "JCODE_INTERNAL_FORCE_FIRST_EDIT_RETRY")) + #t] + [else (loop (cdr xs))])) + #f) + (check-pred! "verified-run: forced first-edit retry recommends edit only" + seen-messages + (lambda (xs) + (let loop ([ys xs]) + (cond + [(null? ys) #f] + [(and (equal? (message-role (car ys)) "user") + (str-contains? (message-content (car ys)) + "only edit/write") + (str-contains? (message-content (car ys)) + "one structured edit") + (not (str-contains? (message-content (car ys)) + "Invalid previous text excerpt"))) + #t] + [else (loop (cdr ys))]))))) (safe-delete-test-file! target-path) (safe-delete-test-file! readme-path))