Constrain pre-edit MCP discovery in verified runs
ober
603a75aaa8fcf4adf5c1d9faf103fdddd0310e42
--- a/src/jcode/core/verified-run.ss +++ b/src/jcode/core/verified-run.ss @@ -603,6 +603,16 @@ (string-has-any? (tool-def-name tool-def) *verified-compact-mcp-name-fragments*)) +(def (pre-edit-mcp-block-message name) + (and (= (current-successful-edit-count) 0) + (let ((next (+ (current-pre-edit-mcp-count) 1))) + (current-pre-edit-mcp-count next) + (and (> next pre-edit-mcp-limit) + (string-append + "pre-edit MCP discovery limit reached while calling " + name + ". Stop routing to MCP/API discovery. The next useful tool must be read/list on the current repo if a local file is still unknown, or edit/write with the first complete draft. After a successful edit and verify failure, use targeted MCP only for concrete syntax/API questions."))))) + (def (env-truthy? name) (let ((v (getenv name))) (and v @@ -630,6 +640,8 @@ (make-tool-spec name desc params) (lambda (args) (or (rejected-draft-hard-recovery-message cwd name) + (let ((msg (pre-edit-mcp-block-message name))) + (and msg (error 'mcp msg))) (tool-execute name (workflow-args->hash args)))) '()))))) (get-tool-schemas))) @@ -649,7 +661,10 @@ "") ".\n") ".\n") - "MCP tools are optional support for language, API, project-specific, or validator facts; caller guidance may forbid or narrow their use, and that caller guidance wins. After verify() fails, the verifier output is the repair target: make the smallest relevant edit and call verify again before broad MCP/API discovery. Use MCP after a failure only for targeted syntax or API facts that block the edit. Keep code changes through edit/write/line_edit/replace_def/replace_range and final validation through verify.\n")))) + "MCP tools are optional support for language, API, project-specific, or validator facts; caller guidance may forbid or narrow their use, and that caller guidance wins. After verify() fails, the verifier output is the repair target: make the smallest relevant edit and call verify again before broad MCP/API discovery. Use MCP after a failure only for targeted syntax or API facts that block the edit. Keep code changes through edit/write/line_edit/replace_def/replace_range and final validation through verify.\n" + "Before the first successful edit, MCP discovery has a hard budget of " + (number->string pre-edit-mcp-limit) + " calls. Use it only to remove concrete uncertainty; then create the first draft with edit/write and verify it.\n")))) (def *tool-path-suffixes* '(".ss" ".scm" ".sls" ".md" ".txt" ".json" ".yaml" ".yml" @@ -788,6 +803,12 @@ (def current-inspections-after-edit (make-parameter 0)) +(def current-successful-edit-count + (make-parameter 0)) + +(def current-pre-edit-mcp-count + (make-parameter 0)) + (def current-last-verify-detail (make-parameter #f)) @@ -801,6 +822,8 @@ (def inspection-after-edit-limit 4) +(def pre-edit-mcp-limit 2) + (def rejected-draft-inspection-limit 2) (def rejected-draft-repeat-threshold 1) @@ -979,6 +1002,7 @@ (reset-existing-ss-rewrite-state!) (reset-path-only-run-state!) (reset-failed-verify-inspections!) + (current-successful-edit-count (+ (current-successful-edit-count) 1)) (current-edited-since-verify? #t) (current-inspections-after-edit 0)) @@ -3363,6 +3387,8 @@ " Call verify now.")))))) (def (coding-workflow verify-cmd cwd . opt) + (current-successful-edit-count 0) + (current-pre-edit-mcp-count 0) (let* ((o (if (pair? opt) (car opt) '())) (scope (or (opt-get o 'write-scope) (current-write-scope))) (run-aliases? (let ((p (assoc 'run-aliases? o))) @@ -3648,6 +3674,8 @@ (current-inspections-after-failed-verify 0) (current-edited-since-verify? #f) (current-inspections-after-edit 0) + (current-successful-edit-count 0) + (current-pre-edit-mcp-count 0) (current-required-range-repair #f)) (if (> k 1) (run-best-of-k wf task (lambda () (provider-responder provider)) k ropt) --- a/test/run.ss +++ b/test/run.ss @@ -2385,7 +2385,46 @@ (str-contains? s "jerboa_test_lookup") (str-contains? s "caller guidance wins") (str-contains? s "After verify() fails") - (str-contains? s "before broad MCP/API discovery"))))) + (str-contains? s "before broad MCP/API discovery") + (str-contains? s "Before the first successful edit") + (str-contains? s "MCP discovery has a hard budget"))))) + +(let* ([vr-dir "/tmp"] + [target "jcode-verified-mcp-budget.txt"] + [target-path (string-append vr-dir "/" target)] + [tool-results '()]) + (guard (e [#t (void)]) (delete-file target-path)) + (let* ([wf (coding-workflow (string-append "grep -q first-draft " target-path) vr-dir)] + [resp (scripted-responder + (list + (list (make-wtool-call "jerboa_test_lookup" '(("q" . "one")) #f)) + (list (make-wtool-call "jerboa_test_lookup" '(("q" . "two")) #f)) + (list (make-wtool-call "jerboa_test_lookup" '(("q" . "three")) #f)) + (list (make-wtool-call "write" (list (cons "path" target) + (cons "content" "first-draft")) + #f)) + (list (make-wtool-call "verify" '() #f)) + (list (make-wtool-call "done" '(("summary" . "mcp-budget-ok")) #f))))]) + (let ([result (run-workflow wf "avoid pre-edit MCP loop" resp + (list (cons 'max-iterations 10) + (cons 'on-message + (lambda (m) + (when (equal? (message-role m) "tool") + (set! tool-results + (cons (message-content m) tool-results)))))))]) + (check! "verified-run: pre-edit MCP budget recovers through write" + result "mcp-budget-ok") + (check-pred! "verified-run: pre-edit MCP budget blocks third lookup" + (reverse tool-results) + (lambda (xs) + (let loop ([ys xs]) + (cond + [(null? ys) #f] + [(and (str-contains? (car ys) "pre-edit MCP discovery limit reached") + (str-contains? (car ys) "edit/write with the first complete draft")) + #t] + [else (loop (cdr ys))])))))) + (guard (e [#t (void)]) (delete-file target-path))) (register-tool! "jerboa_failure_advisor" "Fake MCP failure advisor for verified-run tests."