Add local model do-it mode

ober

7c5bb3b28558cc3bfdabb9de66293deace0ffa57

diff --git a/docs/cli.md b/docs/cli.md
index 4965ce3..248ddd0 100644
--- a/docs/cli.md
+++ b/docs/cli.md
@@ -146,6 +146,7 @@ Available in the REPL and TUI.
 | `/plan` | Enter PLAN mode (read-only; write tools hidden). |
 | `/build` | Enter BUILD mode (read + write). |
 | `/mode` | Show the current mode. |
+| `/doit [on\|off\|status\|max N\|max unlimited]` | Local-only auto-continue mode. When enabled on an `ollama*` or `mlx*` provider, jcode automatically sends a continuation prompt after each stopped assistant turn until the model marks the task done or blocked. |
 | `/clear` | Start a new session. |
 | `/sessions` | List saved sessions. |
 | `/compact` | Show the current session's message count. |
diff --git a/src/jcode/core/agent.ss b/src/jcode/core/agent.ss
index d91ec55..9ef441b 100644
--- a/src/jcode/core/agent.ss
+++ b/src/jcode/core/agent.ss
@@ -12,6 +12,9 @@
         current-provider-override
         current-model-override
         current-compact-agent-context
+        current-do-it-mode?
+        current-do-it-max-continuations
+        do-it-terminal-response?
         get-current-provider
         forge-respond-enforced?
         forge-max-repeated-calls
@@ -53,8 +56,24 @@
 (def current-provider-override (make-parameter #f))
 (def current-model-override (make-parameter #f))
 (def current-compact-agent-context (make-parameter #f))
+(def current-do-it-mode? (make-parameter #f))
+(def current-do-it-max-continuations (make-parameter #f))
 
 (def *small-context-instruction-bytes* 3600)
+(def *do-it-done-marker* "[doit:done]")
+(def *do-it-blocked-marker* "[doit:blocked]")
+(def *do-it-continuation-prompt*
+  (string-append
+    "DO IT mode is still enabled. Continue the original task until it is "
+    "genuinely complete. Do not stop after finishing only one step. Use the "
+    "available tools to inspect, edit, test, and repair as needed.\n\n"
+    "If the task is fully complete, start your reply with exactly "
+    *do-it-done-marker* " and then give a concise final summary.\n"
+    "If you are genuinely blocked waiting for user input or external state, "
+    "start your reply with exactly " *do-it-blocked-marker*
+    " and state the blocker."))
+(def *do-it-stream-notice*
+  "\n\n[doit] model stopped; continuing automatically...\n\n")
 
 (def *small-context-tools*
   '("todowrite"
@@ -240,6 +259,8 @@ Current mode: ~a
 
 ~a
 
+~a
+
 IMPORTANT RULES:
 - Follow this repository's instructions and actual file layout for source paths,
   generated artifacts, and build commands. More specific repo-local instructions,
@@ -269,11 +290,23 @@ Be concise. Prefer edit over write for modifying existing files.
     (current-directory)
     (mode-label (current-mode))
     (system-tool-section)
+    (do-it-instructions)
     (mode-instructions (current-mode))
     (expert-prompt-instructions)
     (system-parallelism-rule)
     (agent-instructions-for-prompt)))
 
+(def (do-it-instructions)
+  (if (current-do-it-mode?)
+    (string-append
+      "DO IT mode is ON for this local-model session. Keep working across "
+      "steps until the user's original task is fully complete. Do not present "
+      "a partial step as final. When the task is fully complete, start the "
+      "terminal reply with exactly " *do-it-done-marker*
+      ". If genuinely blocked waiting for the user or external state, start "
+      "the terminal reply with exactly " *do-it-blocked-marker* ".")
+    "DO IT mode is OFF."))
+
 (def (system-tool-section)
   (cond
     ((compact-agent-context?)
@@ -1321,8 +1354,48 @@ Be concise. Prefer edit over write for modifying existing files.
       params)
     (json-object->string ht)))
 
+(def (do-it-terminal-response? response)
+  (let ((content (and response (message-content response))))
+    (and (string? content)
+         (or (string-contains content *do-it-done-marker*)
+             (string-contains content *do-it-blocked-marker*)))))
+
+(def (do-it-continue-again? response continuation-count)
+  (and (current-do-it-mode?)
+       (not (do-it-terminal-response? response))
+       (let ((limit (current-do-it-max-continuations)))
+         (or (not limit) (< continuation-count limit)))))
+
+(def (emit-do-it-stream-notice!)
+  (let ((cb (current-stream-cb)))
+    (when cb (cb *do-it-stream-notice*))))
+
 (def (agent-run session-id user-input)
-  (log-info logger "agent-run" `((session . ,session-id)))
+  (log-info logger "agent-run"
+    `((session . ,session-id)
+      (do-it . ,(current-do-it-mode?))))
+  (let ((provider (get-current-provider)))
+    (cond
+      ((and (current-do-it-mode?)
+            (not (local-provider? (provider-name provider))))
+       (error 'agent-run
+         (format "DO IT mode is local-only; active provider is '~a'. Switch to a local provider (ollama/mlx) or run /doit off."
+           (provider-name provider))))
+      ((current-do-it-mode?)
+       (agent-run-do-it session-id user-input 0))
+      (else
+       (agent-run-once session-id user-input)))))
+
+(def (agent-run-do-it session-id user-input continuation-count)
+  (let ((response (agent-run-once session-id user-input)))
+    (if (do-it-continue-again? response continuation-count)
+      (begin
+        (emit-do-it-stream-notice!)
+        (agent-run-do-it session-id *do-it-continuation-prompt*
+                         (+ continuation-count 1)))
+      response)))
+
+(def (agent-run-once session-id user-input)
   ;; Defensively repair the session before the new turn — a previously
   ;; cancelled or crashed turn may have left an assistant tool_calls
   ;; message without matching tool result messages, which the
diff --git a/src/jcode/ui/cli.ss b/src/jcode/ui/cli.ss
index 3742d50..26a9bbf 100644
--- a/src/jcode/ui/cli.ss
+++ b/src/jcode/ui/cli.ss
@@ -16,6 +16,7 @@
         :jcode/core/message
         :jcode/core/agent
         :jcode/core/expert
+        :jcode/core/models
         :jcode/core/errors
         :jcode/core/debug-repl
         :jcode/core/skill
@@ -978,11 +979,67 @@ EXAMPLES:
       (unless (run-verified-task (get-current-provider) task bestof max-iterations vcmd cwd scope run-aliases? guidance-file json? status-file)
         (exit 1)))))
 
+(def (do-it-provider-name)
+  (or (current-provider-override) (config-provider)))
+
+(def (do-it-max-label)
+  (let ((n (current-do-it-max-continuations)))
+    (if n (format "~a continuation(s)" n) "unlimited continuations")))
+
+(def (print-do-it-status)
+  (printf "DO IT mode: ~a (~a, provider: ~a~a)~n"
+    (if (current-do-it-mode?) "ON" "OFF")
+    (do-it-max-label)
+    (do-it-provider-name)
+    (if (local-provider? (do-it-provider-name)) ", local" ", not local")))
+
+(def (parse-do-it-max raw)
+  (let ((trimmed (string-trim raw)))
+    (cond
+      ((or (string=? trimmed "unlimited")
+           (string=? trimmed "none")
+           (string=? trimmed "off"))
+       #f)
+      (else
+       (let ((n (string->number trimmed)))
+         (unless (and n (integer? n) (exact? n) (>= n 0))
+           (error 'doit "max requires a non-negative integer or 'unlimited'"))
+         n)))))
+
+(def (handle-do-it-command cmd)
+  (cond
+    ((or (equal? cmd "doit") (equal? cmd "doit status")
+         (equal? cmd "do-it") (equal? cmd "do-it status"))
+     (print-do-it-status))
+    ((or (equal? cmd "doit on") (equal? cmd "do-it on"))
+     (let ((provider (do-it-provider-name)))
+       (if (local-provider? provider)
+         (begin
+           (current-do-it-mode? #t)
+           (printf "DO IT mode: ON for local provider ~a (~a). Use /doit off to stop.~n"
+             provider (do-it-max-label)))
+         (printf "DO IT mode refused: provider ~a is not local. Switch to ollama/mlx first.~n"
+           provider))))
+    ((or (equal? cmd "doit off") (equal? cmd "do-it off"))
+     (current-do-it-mode? #f)
+     (printf "DO IT mode: OFF~n"))
+    ((or (string-prefix? "doit max " cmd)
+         (string-prefix? "do-it max " cmd))
+     (let* ((prefix-len (if (string-prefix? "doit max " cmd)
+                          (string-length "doit max ")
+                          (string-length "do-it max ")))
+            (n (parse-do-it-max
+                 (substring cmd prefix-len (string-length cmd)))))
+       (current-do-it-max-continuations n)
+       (printf "DO IT max: ~a~n" (do-it-max-label))))
+    (else
+     (printf "Usage: /doit [on|off|status|max N| max unlimited]~n"))))
+
 (def (handle-command input session-id)
   (let ((cmd (string-trim (substring input 1 (string-length input)))))
     (cond
       ((equal? cmd "help")
-       (display "\nCommands:\n  /help              Show this help\n  /model [name]      Show or set model\n  /provider [name]   Show or set provider\n  /expert <prompt>   Route one prompt to the configured expert model\n  /plan              Switch to PLAN mode (read-only)\n  /build             Switch to BUILD mode (read+write)\n  /mode              Show current mode\n  /mcp               Toggle MCP tools on/off\n  /tools             List available tools\n  /agents            List named sub-agent roles (task tool)\n  /themes            List available TUI themes\n  /theme [name]      Cycle or switch TUI theme\n  /clear             Start a new session\n  /sessions          List saved sessions\n  /compact           Show message count\n  /undo [N]          Revert last N checkpoint(s) (default 1)\n  /checkpoints       List recent shadow-git checkpoints\n  /forge [on|off]    Show or toggle forge guardrails\n  /forge sampling <off|on|strict>  Per-model sampling policy\n  /forge workflow    Describe + self-test the workflow engine\n  /forge proxy       Describe + self-test the OpenAI-compatible proxy\n  /forge ablation    Describe + self-test the eval/ablation harness\n  /forge verify      Describe + self-test the verify-gate (ATLAS verify+repair)\n  /forge bestofk     Describe + self-test best-of-k diverse-gen (ATLAS Phase-1)\n  /forge breaker     Describe + self-test the no-progress loop breaker\n  /forge run [opts] <task>  Verify-gated coding; opts: --verify, --bestof, --max-iterations, --write-scope, --guidance-file, --json, --status-file, --run-aliases\n  /quit              Exit\n\nMulti-line: end a line with \\ to continue on the next line.\n\n"))
+       (display "\nCommands:\n  /help              Show this help\n  /model [name]      Show or set model\n  /provider [name]   Show or set provider\n  /expert <prompt>   Route one prompt to the configured expert model\n  /plan              Switch to PLAN mode (read-only)\n  /build             Switch to BUILD mode (read+write)\n  /mode              Show current mode\n  /doit [on|off|status|max N| max unlimited]\n                     Local-only auto-continue mode for long tasks\n  /mcp               Toggle MCP tools on/off\n  /tools             List available tools\n  /agents            List named sub-agent roles (task tool)\n  /themes            List available TUI themes\n  /theme [name]      Cycle or switch TUI theme\n  /clear             Start a new session\n  /sessions          List saved sessions\n  /compact           Show message count\n  /undo [N]          Revert last N checkpoint(s) (default 1)\n  /checkpoints       List recent shadow-git checkpoints\n  /forge [on|off]    Show or toggle forge guardrails\n  /forge sampling <off|on|strict>  Per-model sampling policy\n  /forge workflow    Describe + self-test the workflow engine\n  /forge proxy       Describe + self-test the OpenAI-compatible proxy\n  /forge ablation    Describe + self-test the eval/ablation harness\n  /forge verify      Describe + self-test the verify-gate (ATLAS verify+repair)\n  /forge bestofk     Describe + self-test best-of-k diverse-gen (ATLAS Phase-1)\n  /forge breaker     Describe + self-test the no-progress loop breaker\n  /forge run [opts] <task>  Verify-gated coding; opts: --verify, --bestof, --max-iterations, --write-scope, --guidance-file, --json, --status-file, --run-aliases\n  /quit              Exit\n\nMulti-line: end a line with \\ to continue on the next line.\n\n"))
       ((equal? cmd "model")
        (printf "Provider: ~a~n" (or (current-provider-override) (config-provider)))
        (printf "Model:    ~a~n" (or (current-model-override) (config-model)))
@@ -1046,7 +1103,11 @@ EXAMPLES:
        (printf "\x1b;[1;32mMode: BUILD\x1b;[0m (read+write)~n"))
       ((equal? cmd "mode")
        (printf "Current mode: ~a~n"
-         (if (eq? (current-mode) 'plan) "PLAN (read-only)" "BUILD (read+write)")))
+         (if (eq? (current-mode) 'plan) "PLAN (read-only)" "BUILD (read+write)"))
+       (print-do-it-status))
+      ((or (equal? cmd "doit") (string-prefix? "doit " cmd)
+           (equal? cmd "do-it") (string-prefix? "do-it " cmd))
+       (handle-do-it-command cmd))
       ((equal? cmd "clear")
        (let ((new-session (session-create "New session")))
          (printf "Started new session.~n")
diff --git a/src/jcode/ui/tui-input.ss b/src/jcode/ui/tui-input.ss
index 0baf501..2d43318 100644
--- a/src/jcode/ui/tui-input.ss
+++ b/src/jcode/ui/tui-input.ss
@@ -55,6 +55,16 @@
     ("/mode"          . "Show or set PLAN/BUILD mode")
     ("/plan"          . "Switch to PLAN (read-only) mode")
     ("/build"         . "Switch to BUILD (read+write) mode")
+    ("/doit"          . "Local-only auto-continue mode")
+    ("/doit status"   . "Show DO IT mode status")
+    ("/doit on"       . "Enable DO IT mode for local providers")
+    ("/doit off"      . "Disable DO IT mode")
+    ("/doit max"      . "Set DO IT auto-continuation limit")
+    ("/do-it"         . "Alias for /doit")
+    ("/do-it status"  . "Show DO IT mode status")
+    ("/do-it on"      . "Enable DO IT mode for local providers")
+    ("/do-it off"     . "Disable DO IT mode")
+    ("/do-it max"     . "Set DO IT auto-continuation limit")
     ("/clear"         . "Start new session")
     ("/sessions"      . "List saved sessions")
     ("/resume"        . "Resume a stored session in this tab")
diff --git a/src/jcode/ui/tui.ss b/src/jcode/ui/tui.ss
index 60f8747..47dabdf 100644
--- a/src/jcode/ui/tui.ss
+++ b/src/jcode/ui/tui.ss
@@ -587,6 +587,68 @@
   "Toggle between PLAN and BUILD."
   (set-mode! state (if (eq? (current-mode) 'plan) 'build 'plan)))
 
+(def (do-it-provider-name)
+  (or (current-provider-override) (config-provider)))
+
+(def (do-it-max-label)
+  (let ((n (current-do-it-max-continuations)))
+    (if n (format "~a continuation(s)" n) "unlimited continuations")))
+
+(def (do-it-status-message)
+  (format "DO IT mode: ~a (~a, provider: ~a~a)"
+    (if (current-do-it-mode?) "ON" "OFF")
+    (do-it-max-label)
+    (do-it-provider-name)
+    (if (local-provider? (do-it-provider-name)) ", local" ", not local")))
+
+(def (parse-do-it-max raw)
+  (let ((trimmed (string-trim raw)))
+    (cond
+      ((or (string=? trimmed "unlimited")
+           (string=? trimmed "none")
+           (string=? trimmed "off"))
+       #f)
+      (else
+       (let ((n (string->number trimmed)))
+         (unless (and n (integer? n) (exact? n) (>= n 0))
+           (error 'doit "max requires a non-negative integer or 'unlimited'"))
+         n)))))
+
+(def (handle-do-it-command! state cmd)
+  (cond
+    ((or (equal? cmd "doit") (equal? cmd "doit status")
+         (equal? cmd "do-it") (equal? cmd "do-it status"))
+     (add-message! state (msg-block-system (do-it-status-message))))
+    ((or (equal? cmd "doit on") (equal? cmd "do-it on"))
+     (let ((provider (do-it-provider-name)))
+       (if (local-provider? provider)
+         (begin
+           (current-do-it-mode? #t)
+           (add-message! state
+             (msg-block-system
+               (format "DO IT mode: ON for local provider ~a (~a). Use /doit off to stop."
+                 provider (do-it-max-label)))))
+         (add-message! state
+           (msg-block-system
+             (format "DO IT mode refused: provider ~a is not local. Switch to ollama/mlx first."
+               provider))))))
+    ((or (equal? cmd "doit off") (equal? cmd "do-it off"))
+     (current-do-it-mode? #f)
+     (add-message! state (msg-block-system "DO IT mode: OFF")))
+    ((or (string-prefix? "doit max " cmd)
+         (string-prefix? "do-it max " cmd))
+     (let* ((prefix-len (if (string-prefix? "doit max " cmd)
+                          (string-length "doit max ")
+                          (string-length "do-it max ")))
+            (n (parse-do-it-max
+                 (substring cmd prefix-len (string-length cmd)))))
+       (current-do-it-max-continuations n)
+       (add-message! state
+         (msg-block-system (format "DO IT max: ~a" (do-it-max-label))))))
+    (else
+     (add-message! state
+       (msg-block-system "Usage: /doit [on|off|status|max N| max unlimited]")))))
+
 ;; ---- /resume ----
 
 (def (messages->display-blocks msgs)
@@ -659,6 +721,8 @@
                "  /plan       Switch to PLAN mode (read-only)"
                "  /build      Switch to BUILD mode (read+write)"
                "  /mode       Show current mode"
+               "  /doit [on|off|status|max N| max unlimited]"
+               "              Local-only auto-continue mode for long tasks"
                "  /tools      List available tools"
                "  /agents     List named sub-agent roles (task tool)"
                "  /clear      Start new session"
@@ -719,9 +783,15 @@
       ((equal? cmd "mode")
        (add-message! state
          (msg-block-system
-           (if (eq? (current-mode) 'plan)
-             "Current mode: PLAN (read-only)"
-             "Current mode: BUILD (read+write)"))))
+           (string-append
+             (if (eq? (current-mode) 'plan)
+               "Current mode: PLAN (read-only)"
+               "Current mode: BUILD (read+write)")
+             "\n"
+             (do-it-status-message)))))
+      ((or (equal? cmd "doit") (string-prefix? "doit " cmd)
+           (equal? cmd "do-it") (string-prefix? "do-it " cmd))
+       (handle-do-it-command! state cmd))
       ((equal? cmd "model")
        (handle-model-popup! state))
       ((equal? cmd "refresh-models")