Recover verified runs from empty scaffold and MCP budget

ober

cf8a509e5a76673a86b55da9a1ad9f5df8edd476

diff --git a/src/jcode/core/verified-run.ss b/src/jcode/core/verified-run.ss
index c048c27..36e4372 100644
--- a/src/jcode/core/verified-run.ss
+++ b/src/jcode/core/verified-run.ss
@@ -673,10 +673,10 @@
                    (params (or (hash-get fn "parameters") (make-hash-table))))
                (make-tool-def
                  (make-tool-spec name desc params)
-                 (lambda (args)
+	               (lambda (args)
                    (or (rejected-draft-hard-recovery-message cwd name)
                        (let ((msg (pre-edit-mcp-block-message name)))
-                         (and msg (error 'mcp msg)))
+                         (and msg (raise-recoverable-tool-error msg name)))
                        (let ((msg (post-failed-verify-mcp-block-message name)))
                          (and msg
                               (error 'mcp
@@ -3528,8 +3528,9 @@
          (template-kind (normal-script-kind kind)))
     (cond
       ((not path)
-       (error 'create_verified_jerboa_script
-              "missing path arg; use {\"path\":\"main.ss\",\"kind\":\"minimal-pass\"}"))
+       (raise-recoverable-tool-error
+         "create_verified_jerboa_script requires a path. Do not retry empty create_verified_jerboa_script(). For this task use edit(path=\"life.ss\", content=<complete implementation>) rather than a generic scaffold, or call create_verified_jerboa_script(path=\"life.ss\", kind=\"minimal-pass\") only for a trivial starter."
+         'create_verified_jerboa_script))
       ((not (source-ss-path? path))
        (error 'create_verified_jerboa_script
               (format "path must end in .ss for a Jerboa script: ~a" path)))
diff --git a/test/run.ss b/test/run.ss
index 0045fa6..5ce72bb 100644
--- a/test/run.ss
+++ b/test/run.ss
@@ -2467,6 +2467,42 @@
   (safe-delete-test-file! target-path))
 
 (let* ([vr-dir "/tmp"]
+       [target "jcode-verified-empty-scaffold-recovery.txt"]
+       [target-path (string-append vr-dir "/" target)]
+       [tool-results '()]
+       [wf (coding-workflow (string-append "grep -q ok " target) vr-dir)]
+       [resp (scripted-responder
+               (list
+                 (list (make-wtool-call "create_verified_jerboa_script" '() #f))
+                 (list (make-wtool-call "edit" (list (cons "path" target)
+                                                      (cons "content" "ok\n")) #f))
+                 (list (make-wtool-call "verify" '() #f))
+                 (list (make-wtool-call "done" '(("summary" . "empty-scaffold-recovered")) #f))))])
+  (safe-delete-test-file! target-path)
+  (let ([result (run-workflow wf "recover from empty scaffold call" resp
+                  (list (cons 'max-iterations 6)
+                        (cons 'max-tool-errors 0)
+                        (cons 'on-message
+                          (lambda (m)
+                            (when (equal? (message-role m) "tool")
+                              (set! tool-results
+                                (cons (message-content m) tool-results)))))))])
+    (check! "verified-run: empty scaffold call recovers with zero hard errors"
+            result "empty-scaffold-recovered")
+    (check-pred! "verified-run: empty scaffold call gives recoverable path guidance"
+      (reverse tool-results)
+      (lambda (xs)
+        (let loop ([ys xs])
+          (cond
+            [(null? ys) #f]
+            [(and (str-contains? (car ys) "[ToolRecoverableError]")
+                  (str-contains? (car ys) "create_verified_jerboa_script requires a path")
+                  (str-contains? (car ys) "edit(path=\"life.ss\""))
+             #t]
+            [else (loop (cdr ys))])))))
+  (safe-delete-test-file! target-path))
+
+(let* ([vr-dir "/tmp"]
        [fixture "jcode-verified-run-budget-fixture.txt"]
        [fixture-path (string-append vr-dir "/" fixture)]
        [target "jcode-verified-run-budget-out.txt"]
@@ -2766,6 +2802,7 @@
             (cond
               [(null? ys) #f]
               [(and (str-contains? (car ys) "pre-edit MCP discovery limit reached")
+                    (str-contains? (car ys) "[ToolRecoverableError]")
                     (str-contains? (car ys) "edit/write with the first complete draft"))
                #t]
               [else (loop (cdr ys))]))))))