Files
harness-tools/extensions/todo-herdr

Forked from leset0ng/pi-todo-herdr

pi-todo-herdr

Hierarchical, session-local task tracking for Pi, with atomic batch tools, a live task-tree widget, and optional Herdr sidebar integration.

pi-todo-herdr task tree and Herdr sidebar

Features

  • Five focused tools: set_tasks, patch_tasks, rm_task, list_task, and get_task
  • Parent/child task trees with generated integer IDs and atomic append/replace modes
  • Atomic batch mutations: every requested change succeeds or none of it is applied
  • Bounded task input and widget output to keep sessions and terminals responsive
  • Four states: pending, in-progress, blocked, and done
  • Cumulative active-time tracking that pauses outside in-progress and survives session restore
  • Session-branch persistence across reload, resume, fork, tree navigation, and context compaction
  • A concise first-action workflow prompt plus a one-shot post-compaction recovery reminder
  • Compact, theme-aware Pi Todo header and task tree with hierarchy, status colors, timers, and an adaptive progress bar
  • Optional responsive Todo + Subagents panels when the interactive-subagents extension is present
  • Optional Herdr metadata showing the current task in the agent sidebar

Requirements

  • Pi 0.81 or newer
  • Node.js 22.19 or newer
  • Herdr is optional; all task tools and the Pi widget work without it

Install

Install the latest version from npm:

pi install npm:@jbcode/pi-todo-herdr

Then start Pi. If Pi is already running, use /reload to load the extension.

To try the package for one run without installing it:

pi -e npm:@jbcode/pi-todo-herdr

Update or remove it with Pi's package commands:

pi update npm:@jbcode/pi-todo-herdr
pi remove npm:@jbcode/pi-todo-herdr

Usage

For work with three or more distinct steps, the extension instructs Pi to explore enough to understand the scope, then call set_tasks before implementation. Smaller work remains untracked. Pi then updates task states at each real transition instead of reconstructing progress at the end.

After automatic or manual context compaction, the extension sends one short hidden reminder only when unfinished tasks exist. Pi calls list_task to recover the authoritative current IDs and statuses; the full tree is not injected on every model call.

Tool Parameters Behavior
set_tasks { mode, tasks, force } append extends the tree with force: false; replace atomically installs a new tree or clears it with []. Replacing unfinished work requires force: true.
patch_tasks { tasks: [...] } Atomically patches multiple tasks. Use null for unchanged text/status, parentId: -1 for unchanged or 0 to move to root, and position: -1 to keep the current order. Completed trees may auto-clear; do not recreate cleared completed tasks.
rm_task { ids: [...], reason } Removes only mistaken, duplicate, or canceled tasks. Removing a parent cascades through its descendants.
list_task {} Returns the full tree as compact id/status/name lines.
get_task { ids: [...] } Returns complete task details, including parent/child IDs and timing for tasks that have started.

Example append call:

{
  "mode": "append",
  "tasks": [
    {
      "name": "Implement authentication",
      "description": "Add session validation and route protection.",
      "status": "in-progress",
      "parentId": null,
      "children": [
        {
          "name": "Add auth middleware",
          "description": "Validate the session before protected handlers run.",
          "status": "pending",
          "parentId": null,
          "children": []
        }
      ]
    }
  ],
  "force": false
}

Task input keys are explicit so strict tool-schema bridges do not invent values for omitted properties. Use description: null when no detail is needed, status: "pending" for the default state, parentId: null for root tasks and nested children, and children: [] for leaves. prepareArguments still expands legacy calls that omit these fields.

To keep model-generated input safe and bounded, a session may contain up to 200 tasks. One mutation may affect up to 50 explicitly supplied tasks. The widget header acts as a virtual root above three stored task levels: categories, items, and leaf sub-items. Names are limited to 200 Unicode characters and descriptions to 4,000 Unicode characters. Names and descriptions reject terminal controls and bidirectional override characters; descriptions may still contain line feeds for normal multi-line detail.

IDs are not reused by append/remove operations; replace deliberately rebuilds IDs from 1. Replace forbids non-null parentId, so new hierarchy must use nested children. Multiple tasks may be in-progress. A parent can be marked done only when all descendants are also done.

A task starts timing the first time it enters in-progress. Leaving that state records a stop and pauses the timer; entering it again resumes with a new active segment while preserving the cumulative duration. Repeated updates with the same status, as well as name, description, hierarchy, and position changes, do not reset timing. For started tasks, get_task includes the first startedAt timestamp, a stoppedAt timestamp while paused, and cumulative durationMs. Timestamps use ISO 8601.

Pi Widget

The widget appears above Pi's editor while todos exist. Run /todo to hide or show it for the current Pi runtime. Ten seconds after every task is completed, the task tree is automatically deleted and task IDs reset to 1; adding or reopening unfinished work during that delay cancels the clear. Its compact, unboxed header retains the themed Todos title, done/total count, blocked-work count when present, and an adaptive progress bar; the bar drops away first at narrow widths. Body rows retain the task tree's virtual-root connectors, hierarchy, statuses, and timers. The outline uses only the labels needed by the deepest task: one-level trees render every task as an unlabeled leaf, two-level trees use numbered parents and unlabeled leaves, and three-level trees use Roman-numeral categories, numbered items, and unlabeled leaves. Internal task IDs remain unchanged and visible in tool output. In-progress circles rotate through ◐ ◓ ◑ ◒ twice per second; other status symbols remain still. The standalone widget shows at most 12 task rows plus a compact count of hidden tasks.

Todos 2/6 · !1  ━━━━────
  ├─ I. ◐ Build auth · 12:08
  │  ├─ 1. ! Resolve token issue · 03:41
  │  └─    ○ Inspect provider response
  └─ II. ✓ Define schema · 04:31

When the optional interactive-subagents extension exposes its statusWidget integration, Todo claims the Subagents panel only while both extensions have content. The duck-typed widget must provide hasContent(), getBodyRowCount(), render(width, maxBodyRows?, theme?), claim(onChange), and isClaimed(onChange). The optional theme is the current Pi theme object with the structural fg(color, text) formatter; Todo passes that exact object unchanged, while older providers may ignore the third argument. isClaimed must return true only for the exact callback supplied by the current runtime claim; this lets Todo recover safely after a provider reload or invalidation. Above 120 columns they render side by side with a one-cell gap and up to ten body rows per panel; the shorter panel is padded with plain spaces. At 120 columns or fewer, Todo renders first and Subagents below it with one blank line between sections. Stacked panels share ten body rows, preserving one row for each panel and reporting overflow honestly. If the extension is absent or exposes an older API, Todo falls back to its full-width Todo widget.

Tool calls remain compact in the conversation. Expand a tool result with Pi's normal tool-output shortcut to inspect affected tasks or full descriptions.

Herdr Integration

When Pi runs inside Herdr, the extension reports the most specific in-progress task as a task pane metadata token. If nothing is in progress, it falls back to a blocked task. Parallel current tasks use a separate task_count token so Herdr can truncate long task names responsively while keeping the remaining count visible, for example #7 Implement auth · +2. The task_progress token reports aggregate completion as done/total while tasks remain unfinished and is omitted once all tasks are complete.

Add $task, $task_count, and $task_progress to the Herdr agent sidebar in ~/.config/herdr/config.toml:

[ui.sidebar.agents]
rows = [["state_icon", "workspace", "tab"], ["$ask", "$ask_count"], ["$task_progress", "$task", "$task_count"]]

Apply the change to a running Herdr server:

herdr server reload-config

Metadata failures never block task operations. The extension does not change Herdr's agent lifecycle or notification state.

Development

Development uses the monorepo's pinned Bun toolchain and shared catalog.

bun install --frozen-lockfile
bun run --filter '@jbcode/pi-todo-herdr' typecheck
bun run --filter '@jbcode/pi-todo-herdr' test
(cd extensions/todo-herdr && bun pm pack --destination ../../dist)

The package has no third-party runtime dependencies. Pi's runtime packages are declared as peers and supplied by Pi.

License

MIT