@thaning0/openclaw-plugin-todo-list
v2.3.0
Published
Session-scoped todo tracking plugin for OpenClaw agents with tolerant full-snapshot writes, reset support, and drift reminders.
Maintainers
Readme
@thaning0/openclaw-plugin-todo-list
A simple todo list plugin for OpenClaw agents.
Claude Code, GitHub Copilot, and Codex all ship with built-in task-tracking workflows, but OpenClaw currently does not. This plugin exists to fill that gap with a simple, session-scoped todo system for OpenClaw agents.
It is adapted from the ideas and code shape behind Claude Code's TodoWriteTool, then simplified for OpenClaw.
This plugin addresses:
- A todo_list tool that stores the complete current plan for the session.
- Prompt injection that keeps the model aligned with that plan before it continues working.
That combination improves long-horizon execution, reduces skipped steps, and makes the agent more disciplined about implementation, testing, and final verification.
How It Works
- For multi-step or non-trivial work, the agent initializes the todo list with the full current snapshot.
- Every later todo_list call submits the entire latest snapshot.
- If the model forgets to keep exactly one current todo, the plugin repairs the snapshot and records the correction.
- If the agent keeps working for too long without updating the plan, the plugin queues a reminder with the unfinished snapshot.
- When all todos close out, the tool result appends a soft verification reminder before the agent sends its final answer.
The plugin persists state per session, restores unfinished todos on resumed sessions, and keeps each subagent session isolated with its own todo state.
Install
openclaw plugins install @thaning0/openclaw-plugin-todo-listOr add it manually to your openclaw.json:
{
"plugins": {
"entries": {
"todo-list": {
"enabled": true,
"config": {}
}
},
"installs": {
"todo-list": {
"source": "npm",
"spec": "@thaning0/openclaw-plugin-todo-list"
}
}
}
}Quick Example
Input:
{
"reset": false,
"todos": [
{
"id": "todo_001",
"content": "Inspect plugin implementation",
"status": "in_progress"
},
{
"id": "todo_002",
"content": "Implement the change",
"status": "pending"
},
{
"id": "todo_003",
"content": "Verify behavior",
"status": "pending"
}
]
}Typical tool result:
Current: Inspect plugin implementation
Next: Implement the change ( Remaining 3 )Typical reminder after plan drift:
[Todo Reminder]
You have made 3 non-todo tool calls since the last todo_list update.
Stay aligned with the current todo snapshot before doing more work.
If the active work has changed, call todo_list with the full updated snapshot before continuing.
Current snapshot:
Inspect plugin implementation
Implement the change
Verify behaviorTool Contract
The plugin exposes one tool, todo_list by default.
In OpenClaw terms, the tool description handles the usage boundary and base call shape, while before_prompt_build carries the richer workflow rules and live todo state.
Todo item fields:
| Field | Type | Required | Description | |------|------|----------|-------------| | id | string | No | Stable todo id. Reuse it when updating an existing todo. New todos may omit it and the plugin assigns one. | | content | string | Yes | Short action-oriented todo content. | | status | string | Yes | One of pending, in_progress, completed, failed. | | notes | string | No | Optional short notes. | | blockedById | string | No | Optional blocker link. Only valid on pending todos and must reference another unfinished todo in the same snapshot. |
Top-level write fields:
| Field | Type | Required | Description | |------|------|----------|-------------| | todos | array | Yes | Full todo snapshot to persist for the current session. | | reset | boolean | No | When true, discard the previous todo list and replace it with the submitted snapshot. Empty arrays are allowed. |
Rules Enforced By The Plugin
- Every call must submit the complete latest snapshot.
- Todo ids are reused when provided; missing ids are assigned automatically.
- When unfinished work remains, the tool auto-corrects missing or duplicated in_progress states to a single current todo.
- If a todo becomes the repaired current item, any blockedById on that todo is cleared because only pending todos may stay blocked.
- Todos must stay ordered as completed or failed items first, then the single in_progress todo, then pending todos.
- The current todo cannot be removed or skipped unless it remains in_progress, becomes completed or failed, or performs a valid blocker handoff.
- Future pending todos may be reordered or removed as the plan changes.
- Pending todos cannot jump directly to completed or failed, and terminal todos cannot be reopened.
- When reset is true, the previous list is discarded and transition checks from the old current todo are skipped.
- When all submitted todos are terminal, the active list is closed out and the tool result appends a soft verification reminder.
Tool result details include oldTodos, newTodos, changeSummary, currentTodo, nextTodo, latestCompletedTodo, closedOutList, reset, appliedCorrections, and verificationNudgeNeeded.
When the tool repairs a write, details.appliedCorrections records whether a todo was promoted, which duplicated current todos were demoted, whether the list had to be reordered, and which todos had blockedById cleared during repair.
Prompt Injection
This is the core reason the plugin helps with long-running execution instead of acting like passive storage.
On the first prompt build of a fresh subagent session before any todo writes, the plugin inlines both the static guidance block and the dynamic state block into prompt context. After that initial injection, user-triggered turns continue to receive:
- A static guidance block via prependSystemContext
- A dynamic state block via prependContext
If a drift reminder becomes pending during a tool loop, the plugin first appends the reminder block directly to the triggering tool result so the model sees it immediately. If the reminder is still pending on a later prompt build, prependContext appends the same block after the normal Todo State block.
After the first injection, non-user prompt builds stay quiet unless a drift reminder is still pending. In that case the plugin injects only the reminder block instead of repeating the static guidance.
The examples below use the default tool name todo_list. If you override toolName, the injected text uses your configured name.
Static Guidance Block
[Todo List Guidance]
Use the todo_list tool only when the request benefits from explicit progress tracking.Once you start using it, submit the full current todo snapshot on every update.Reuse ids for existing todos; new todos may omit id and the tool will assign one.Set reset to true to replace the existing todo list with a brand-new full snapshot.First User Turn With No Unfinished Todos
[Todo State]
No unfinished todos.
First decide whether this request is clearly multi-step, long-running, or benefits from progress tracking.`If yes, create the full current snapshot in one todo_list call with a todos array before doing the work. If not, keep the todo list empty.`When unfinished work exists, the tool keeps one todo as \"${TODO_STATUS.N_PROGRESS}\" and later todos stay \"${TODO_STATUS.PENDING}\" until they become current. Do not skip the current todo. If work is blocked, create a new blocker todo and move the blocked todo back to pending with blockedById set to that blocker id. If the plan should be replaced wholesale, call todo_list with reset set to true and the full replacement snapshot.
Later User Turns With No Unfinished Todos
[Todo State]
No unfinished todos.
If this request is multi-step, think first and then initialize the full current snapshot in one todo_list call.
Use reset in todo_list when you need to replace the prior plan wholesale.
If the request is trivial or single-step, keep the todo list empty.User Turns With Active Todos
When unfinished todos exist, the dynamic prompt stays compact and reports the current execution point:
[Todo State]
Current todo: <current content> (<current id>)
Next todo: <next content> (<next id>)
Remaining todos: <count>
Later todos: <optional preview>The tool result text returned by todo_list is intentionally short and only includes populated status lines:
Current: <current content>
Next: <next content> ( Remaining <unfinished count> )
Completed: <most recently completed or failed todo in this write>If Next is missing, the Remaining suffix is omitted. If no todo fields are populated, the tool returns No tasks.
If previously persisted state is invalid, the dynamic prompt reports the first violation and instructs the model to repair the full snapshot before continuing.
Drift Reminder
When unfinished work exists, the plugin counts successful non-todo tool calls after the last successful todo write. Once the count reaches driftReminderToolCallThreshold, it queues a reminder. When the threshold is crossed during a non-todo tool loop, the reminder block is appended to that tool's persisted result message first.
Close-Out Verification Reminder
When all submitted todos are terminal, the plugin closes out the active list and appends a soft reminder to the tool result.
The default reminder is:
[Verification Reminder] Before sending the final answer, check the results of the previous tasks once more.This does not modify the todo snapshot and does not reopen the list. It only nudges the agent to do a final pass before responding to the user.
Blocker Handoff
When the current work is blocked, create a new blocker todo with an explicit id and make it the only in_progress item in the next snapshot:
{
"todos": [
{
"id": "todo_003",
"content": "Resolve malformed payload edge case",
"status": "in_progress"
},
{
"id": "todo_001",
"content": "Implement API validation",
"status": "pending",
"blockedById": "todo_003"
},
{
"id": "todo_002",
"content": "Update tests",
"status": "pending"
}
]
}The blocker todo must be newly created for that handoff. Reusing an existing pending todo as the blocker is rejected. It appears first in the unfinished section because the first unfinished todo is always the current todo.
Configuration
All options are optional.
| Option | Type | Default | Description | |--------|------|---------|-------------| | toolName | string | "todo_list" | Tool name exposed to the model | | dataDir | string | ~/.openclaw/plugins-data/todo-list/sessions | Directory for session state files | | maxPromptTasks | number | 7 | Maximum number of pending todo titles summarized in prompt context | | driftReminderEnabled | boolean | true | Enable drift reminders after repeated successful non-todo tool calls while unfinished work remains | | driftReminderToolCallThreshold | number | 3 | Successful non-todo tool calls required after the last todo write before the first reminder is queued | | driftReminderRepeatInterval | number | 3 | Additional successful non-todo tool calls required before the reminder can be queued again | | driftReminderMaxTodos | number | 5 | Maximum unfinished todos rendered inside a drift reminder block | | driftReminderMessage | string | built-in message | Override the main drift reminder message shown before the unfinished snapshot | | verificationNudgeMinTasks | number | 3 | Deprecated and ignored. Kept only for backward-compatible config parsing | | verificationNudgeMessage | string | built-in message | Override the soft verification reminder appended to tool results when the todo list closes out |
Example:
{
"plugins": {
"entries": {
"todo-list": {
"enabled": true,
"config": {
"toolName": "tasks",
"driftReminderToolCallThreshold": 4,
"driftReminderRepeatInterval": 2,
"verificationNudgeMessage": "Before sending the final answer, check the finished task results one more time."
}
}
}
}
}Channel Status Notices
When verbose is set to on or full for the agent, the plugin sends a Todo Status style message to the active channel after successful todo writes.
|||
|---|---|
|
|
|
Session Restore
When a session is resumed from a previous session through resumedFrom, unfinished todos are carried over automatically.
State Persistence
Todo state is stored as JSON files under dataDir, one file per session. Writes use atomic rename to prevent corruption. V1 state files are migrated on load by mapping tasks to todos, title to content, and done to completed.
Requirements
- OpenClaw >= 2026.3.28
- Node.js >= 22
