todo fix

ober

fccf434206fed4101ff4cd5a734bb651906a9579

diff --git a/docs/tools.md b/docs/tools.md
index 76189d1..c72bf70 100644
--- a/docs/tools.md
+++ b/docs/tools.md
@@ -43,10 +43,15 @@ page is the catalogue plus the safety model that wraps every call.
 
 | Tool | Purpose | Parameters |
 |---|---|---|
+| `todowrite` | Update the visible task plan in the TUI sidebar | `todos[]` of `{content,status,priority?}` |
 | `batch` | Run several tool calls in parallel (green threads) | `calls[]` of `{tool,args}` |
 | `task` | Spawn a sub-agent with its own context | `description`, `prompt`, `system?`, `agent?`, `task_id?` |
 | `repomap` | Condensed project map (top files by PageRank + key symbols) | *(none)* |
 
+`todowrite` is for multi-step work. The model sends the complete current list
+with statuses `pending`, `in_progress`, `completed`, or `cancelled`; the TUI
+renders it on the right sidebar above the modified-file list.
+
 `task`'s `system` argument can name a skill, so a sub-agent can be launched with
 a specialized prompt. LSP lookups (`lsp_definition` / `lsp_hover` /
 `lsp_references`) exist internally but are **not** exposed to the model — they
diff --git a/docs/tui.md b/docs/tui.md
index 0ff09c7..1f4c273 100644
--- a/docs/tui.md
+++ b/docs/tui.md
@@ -10,10 +10,11 @@ rendering, live diffs, themes, and a stats sidebar.
 ┌──────────────────────────────────────┬────────────────────┐
 │ message thread                        │ sidebar (≥100 cols)│
 │   user / assistant / tool blocks      │  Sessions          │
-│   markdown + syntax + diffs           │  Files Changed     │
-│                                       │  System (CPU/GPU)  │
+│   markdown + syntax + diffs           │  System (CPU/GPU)  │
 │                                       │  Tools             │
 │  ⠋ working…                           │  Connections       │
+│                                       │  Todo              │
+│                                       │  Modified Files    │
 ├──────────────────────────────────────┴────────────────────┤
 │ › your input (wraps, up to 8 rows)                         │
 ├────────────────────────────────────────────────────────────┤
@@ -24,10 +25,11 @@ rendering, live diffs, themes, and a stats sidebar.
 - **Message thread** — the conversation; the spinner row beneath it animates
   while the agent is working.
 - **Sidebar** — 24 columns, shown when the terminal is at least 100 wide.
-  Sections: **Sessions** (current marked `●`), **Files Changed** (git M/A/D),
-  **System** (CPU / MEM / GPU bars, or a stacked [memory breakdown](providers.md#hardware-tiers)
-  for local models), **Tools** (name + call count), **Connections** (MCP servers
-  and LSP status).
+  Sections: **Sessions** (current marked `●`), **System** (CPU / MEM / GPU
+  bars, or a stacked [memory breakdown](providers.md#hardware-tiers) for local
+  models), **Tools** (name + call count), **Connections** (MCP servers and LSP
+  status), **Todo** (the current `todowrite` plan), and **Modified Files** (git
+  M/A/D) at the bottom.
 - **Input** — multi-line, wraps to a maximum of 8 visible rows.
 - **Status bar** — mode (PLAN yellow / BUILD green), model, token counts
   (`in/out`, k/M-formatted), cache-hit rate, and accumulated cost.
diff --git a/src/jcode/core/agent.ss b/src/jcode/core/agent.ss
index df600c1..54af67e 100644
--- a/src/jcode/core/agent.ss
+++ b/src/jcode/core/agent.ss
@@ -53,7 +53,8 @@
 (def *small-context-instruction-bytes* 3600)
 
 (def *small-context-tools*
-  '("apply_patch" "bash" "batch" "edit" "edit_block" "multi-edit" "patch"
+  '("todowrite"
+    "apply_patch" "bash" "batch" "edit" "edit_block" "multi-edit" "patch"
     "read" "write" "ls" "glob" "grep" "repomap"
     "git_status" "git_diff" "git_log" "git_show" "git_commit"
     "mcp_jerboa_jerboa"))
@@ -236,8 +237,9 @@ dropped — emit a real tool_call message instead.
 When the user asks you to do something:
 1. Use grep, glob, ls, or repomap to FIND the relevant source files for this repository
 2. READ those files to see what's actually there
-3. Make minimal targeted edits with the edit tool
-4. Report back with results
+3. For multi-step work, use todowrite to keep the visible todo list current; mark exactly one item in_progress when actively working.
+4. Make minimal targeted edits with the edit tool
+5. Report back with results
 
 Be concise. Prefer edit over write for modifying existing files.
 ~a"
@@ -252,7 +254,7 @@ Be concise. Prefer edit over write for modifying existing files.
 (def (system-tool-section)
   (cond
     ((compact-agent-context?)
-     "Structured function tools are available through the API schema. Use exact schema arg names. Prefer ls/read/grep/glob/repomap to inspect, edit/apply_patch for changes, bash for verification, and git_status/git_diff/git_commit for commits.")
+     "Structured function tools are available through the API schema. Use exact schema arg names. Prefer todowrite for multi-step progress, ls/read/grep/glob/repomap to inspect, edit/apply_patch for changes, bash for verification, and git_status/git_diff/git_commit for commits.")
     (else
      (string-append
        "You have access to these tools:\n"
diff --git a/src/jcode/tool/todo.ss b/src/jcode/tool/todo.ss
new file mode 100644
index 0000000..ea6a63d
--- /dev/null
+++ b/src/jcode/tool/todo.ss
@@ -0,0 +1,70 @@
+;;; jcode todo tool — plan/todo list updates for the TUI sidebar.
+
+(export init-todo-tool)
+
+(import :jcode/tool/registry
+        :jerboa/core
+        :jerboa/runtime)
+
+(def (init-todo-tool)
+  (register-tool! "todowrite"
+    (string-append
+      "Update the current task plan/todo list. Use this for multi-step work, "
+      "plans, and progress tracking. Each todo has content and status: "
+      "pending, in_progress, completed, or cancelled.")
+    (make-todo-schema)
+    handle-todowrite))
+
+(def (make-todo-schema)
+  (let ((schema (make-hash-table))
+        (props (make-hash-table))
+        (todos-prop (make-hash-table))
+        (items (make-hash-table))
+        (item-props (make-hash-table))
+        (content-prop (make-hash-table))
+        (status-prop (make-hash-table))
+        (priority-prop (make-hash-table)))
+    (hash-put! content-prop "type" "string")
+    (hash-put! content-prop "description" "Brief description of the task")
+    (hash-put! status-prop "type" "string")
+    (hash-put! status-prop "description" "pending, in_progress, completed, or cancelled")
+    (hash-put! priority-prop "type" "string")
+    (hash-put! priority-prop "description" "Optional priority: high, medium, or low")
+    (hash-put! item-props "content" content-prop)
+    (hash-put! item-props "status" status-prop)
+    (hash-put! item-props "priority" priority-prop)
+    (hash-put! items "type" "object")
+    (hash-put! items "properties" item-props)
+    (hash-put! items "required" '("content" "status"))
+    (hash-put! todos-prop "type" "array")
+    (hash-put! todos-prop "description" "The complete current todo list")
+    (hash-put! todos-prop "items" items)
+    (hash-put! props "todos" todos-prop)
+    (hash-put! schema "type" "object")
+    (hash-put! schema "properties" props)
+    (hash-put! schema "required" '("todos"))
+    schema))
+
+(def (todo-status todo)
+  (if (hash-table? todo)
+    (hash-ref todo "status" "pending")
+    "pending"))
+
+(def (completed-status? status)
+  (or (equal? status "completed")
+      (equal? status "cancelled")))
+
+(def (todo-summary todos)
+  (let* ((total (length todos))
+         (done (length (filter (lambda (todo)
+                                 (completed-status? (todo-status todo)))
+                               todos))))
+    (format "Todo list updated: ~a of ~a complete" done total)))
+
+(def (handle-todowrite args)
+  (let ((todos (hash-ref args "todos" #f)))
+    (cond
+      ((not (list? todos))
+       "Error: todowrite requires a todos array")
+      (else
+       (todo-summary todos)))))
diff --git a/src/jcode/ui/cli.ss b/src/jcode/ui/cli.ss
index 3e4309a..555670c 100644
--- a/src/jcode/ui/cli.ss
+++ b/src/jcode/ui/cli.ss
@@ -22,6 +22,7 @@
         :jcode/tool/file
         :jcode/tool/apply-patch
         :jcode/tool/bash
+        :jcode/tool/todo
         :jcode/tool/task
         :jcode/tool/repomap-tool
         :jcode/tool/web
@@ -194,6 +195,7 @@
   (init-file-tools)
   (init-apply-patch-tool)
   (init-bash-tool)
+  (init-todo-tool)
   (init-task-tool)
   (init-repomap-tool)
   (init-web-tools)
diff --git a/src/jcode/ui/tui-sidebar.ss b/src/jcode/ui/tui-sidebar.ss
index 2a3aaab..2810c12 100644
--- a/src/jcode/ui/tui-sidebar.ss
+++ b/src/jcode/ui/tui-sidebar.ss
@@ -4,9 +4,10 @@
 (export
   make-sidebar-state sidebar-state? make-fresh-sidebar
   sidebar-state-sessions-set! sidebar-state-files-set!
+  sidebar-state-todos-set!
   sidebar-state-tools-set! sidebar-state-mcp-set! sidebar-state-lsp-set!
   sidebar-state-current-session sidebar-state-sessions
-  sidebar-state-files sidebar-state-tools
+  sidebar-state-files sidebar-state-todos sidebar-state-tools
   sidebar-state-mcp sidebar-state-lsp
   render-sidebar!)
 
@@ -23,13 +24,14 @@
   (sessions      ;; list of (id . title)
    current-session ;; id string
    files          ;; list of (status . path) — from git status
+   todos          ;; list of todo hash tables/alists from todowrite
    tools          ;; list of (name . count)
    mcp            ;; list of (name . tool-count) or '()
    lsp)           ;; string name or #f
   transparent: #t)
 
 (def (make-fresh-sidebar)
-  (make-sidebar-state '() "" '() '() '() #f))
+  (make-sidebar-state '() "" '() '() '() '() #f))
 
 ;; ---- Rendering ----
 
@@ -65,9 +67,6 @@
                ;; Sessions section
                (row (render-section! "Sessions" x y width max-row tfg tbg dfg bg #t))
                (row (render-sessions! sidebar cx row cw max-row fg bg))
-               ;; Files section
-               (row (render-section! "Files Changed" x row width max-row tfg tbg dfg bg #f))
-               (row (render-files! sidebar cx row cw max-row fg bg))
                ;; System section (above Tools so CPU/MEM/GPU stay visible)
                (row (render-section! "System" x row width max-row tfg tbg dfg bg #f))
                (row (render-system! sysmon memstats cx row cw max-row fg bg))
@@ -75,8 +74,17 @@
                (row (render-section! "Tools" x row width max-row tfg tbg dfg bg #f))
                (row (render-tools! sidebar cx row cw max-row fg bg))
                ;; Connections section
-               (row (render-section! "Connections" x row width max-row tfg tbg dfg bg #f)))
-          (render-connections! sidebar cx row cw max-row fg bg))
+               (row (render-section! "Connections" x row width max-row tfg tbg dfg bg #f))
+               (row (render-connections! sidebar cx row cw max-row fg bg))
+               ;; Plan/todo and modified files live at the bottom.
+               (row (if (null? (sidebar-state-todos sidebar))
+                      row
+                      (render-section! "Todo" x row width max-row tfg tbg dfg bg #f)))
+               (row (if (null? (sidebar-state-todos sidebar))
+                      row
+                      (render-todos! sidebar cx row cw max-row fg bg)))
+               (row (render-section! "Modified Files" x row width max-row tfg tbg dfg bg #f)))
+          (render-files! sidebar cx row cw max-row fg bg))
 
         (render-sidebar-bottom! x bottom-row width dfg bg)))))
 
@@ -139,6 +147,47 @@
                       display))
          (loop (cdr files) (+ row 1)))))))
 
+(def (todo-field item key default)
+  (cond
+    ((hash-table? item) (hash-ref item key default))
+    ((list? item)
+     (let ((hit (assoc (string->symbol key) item)))
+       (if hit (cdr hit) default)))
+    (else default)))
+
+(def (todo-done? status)
+  (or (equal? status "completed")
+      (equal? status "cancelled")))
+
+(def (todo-marker status)
+  (cond
+    ((todo-done? status) "✓")
+    ((equal? status "in_progress") "•")
+    (else " ")))
+
+(def (fit-line s width)
+  (cond
+    ((<= width 0) "")
+    ((<= (string-length s) width) s)
+    ((<= width 1) "…")
+    (else (string-append (substring s 0 (- width 1)) "…"))))
+
+(def (render-todos! sidebar x row width max-row fg bg)
+  (let loop ((todos (sidebar-state-todos sidebar)) (row row))
+    (cond
+      ((or (null? todos) (>= row max-row)) row)
+      (#t
+       (let* ((todo (car todos))
+              (status (todo-field todo "status" "pending"))
+              (content (todo-field todo "content" ""))
+              (color (cond
+                       ((equal? status "in_progress") (face-fg-attr 'status-cost))
+                       ((todo-done? status) (face-fg-attr 'sidebar-item))
+                       (else fg)))
+              (display (string-append "[" (todo-marker status) "] " content)))
+         (tb-print! x row color bg (fit-line display width))
+         (loop (cdr todos) (+ row 1)))))))
+
 (def (render-tools! sidebar x row width max-row fg bg)
   (let loop ((tools (sidebar-state-tools sidebar)) (row row))
     (cond
diff --git a/src/jcode/ui/tui.ss b/src/jcode/ui/tui.ss
index 558af40..1e2b697 100644
--- a/src/jcode/ui/tui.ss
+++ b/src/jcode/ui/tui.ss
@@ -34,6 +34,7 @@
         :jcode/tool/file
         :jcode/tool/apply-patch
         :jcode/tool/bash
+        :jcode/tool/todo
         :jcode/tool/task
         :jcode/tool/repomap-tool
         :jcode/tool/web
@@ -274,6 +275,7 @@
   (init-file-tools)
   (init-apply-patch-tool)
   (init-bash-tool)
+  (init-todo-tool)
   (init-task-tool)
   (init-repomap-tool)
   (init-web-tools)
@@ -700,7 +702,8 @@
          (app-state-cache-creation-set! state 0)
          (app-state-cost-set! state 0.0)
          (app-state-tool-counts-set! state (make-hash-table))
-         (app-state-active-tools-set! state '())))
+         (app-state-active-tools-set! state '())
+         (sidebar-state-todos-set! (app-state-sidebar state) '())))
       ((equal? cmd "tools")
        (add-message! state
          (msg-block-system (string-append "Tools: " (string-join (list-tools) ", ")))))
@@ -1981,12 +1984,15 @@
      ;; Reset stream buffer so next round creates a fresh assistant block
      (finalize-last-assistant! state)
      (app-state-stream-buf-set! state "")
-     ;; Track tool counts in sidebar only — no inline message block
-     (let ((tc (app-state-tool-counts state)))
-       (hash-put! tc name (+ 1 (or (hash-get tc name) 0))))
-     (app-state-active-tools-set! state
-       (cons name (app-state-active-tools state)))
-     (update-sidebar-tools! state)
+     (if (equal? name "todowrite")
+       (update-sidebar-todos! state args)
+       (begin
+         ;; Track tool counts in sidebar only — no inline message block
+         (let ((tc (app-state-tool-counts state)))
+           (hash-put! tc name (+ 1 (or (hash-get tc name) 0))))
+         (app-state-active-tools-set! state
+           (cons name (app-state-active-tools state)))
+         (update-sidebar-tools! state)))
      (app-state-dirty?-set! state #t))
     ((end)
      (app-state-active-tools-set! state
@@ -2115,6 +2121,12 @@
     (sidebar-state-tools-set! (app-state-sidebar state)
       (hash->list tc))))
 
+(def (update-sidebar-todos! state args)
+  (when (hash-table? args)
+    (let ((todos (hash-get args "todos")))
+      (when (list? todos)
+        (sidebar-state-todos-set! (app-state-sidebar state) todos)))))
+
 ;; ---- Theme cycling ----
 
 (def (cycle-theme!)
diff --git a/test/run.ss b/test/run.ss
index e873751..6f2faba 100644
--- a/test/run.ss
+++ b/test/run.ss
@@ -16,6 +16,7 @@
         (jcode tool registry)
         (jcode tool file)
         (jcode tool bash)
+        (jcode tool todo)
         (jcode tool external-llm)
         (jcode guardrails nudge)
         (jcode guardrails error-tracker)
@@ -156,6 +157,7 @@
 (current-log-level 'warn)
 (init-file-tools)
 (init-bash-tool)
+(init-todo-tool)
 
 ;; ── Message tests ─────────────────────────────────────────────────
 
@@ -277,6 +279,13 @@
 (let ([r (tool-execute "bash" (args "command" "exit 42"))])
   (check-pred! "bash non-zero exit shown" r (lambda (s) (str-contains? s "Exit code"))))
 
+(let* ([todo1 (args "content" "Inspect sidebar" "status" "completed")]
+       [todo2 (args "content" "Wire todo list" "status" "in_progress")]
+       [r (tool-execute "todowrite" (args "todos" (list todo1 todo2)))])
+  (check-pred! "todowrite progress summary"
+    r
+    (lambda (s) (str-contains? s "1 of 2 complete"))))
+
 ;; ── TUI rendering ─────────────────────────────────────────────────
 
 (section "=== tui rendering ===")