Deep Dive
Todo List System
When a task is too big for a single pass, RaggieCode creates a todo list. Tasks are planned upfront, reviewed by you, then executed one at a time by isolated subagents. If the session crashes, the todo list survives and can be resumed.
How It Works
The todo list system is RaggieCode's mechanism for tackling complex, multi-step tasks that cannot be completed in a single LLM response. Instead of the agent jumping straight into execution, it first creates a plan, presents it to you for approval, and only then begins work .; one task at a time, each handled by an isolated subagent.
Why a todo list system?
A single prompt-response loop works well for small, atomic changes. But real-world tasks like "migrate from SQLite to PostgreSQL" or "add input validation to the login endpoint and update all callers" require multiple steps across many files. The todo list system provides:
The Workflow
Every todo list follows the same six-step pipeline. The agent checks for existing lists first (for crash recovery), creates a new one, adds tasks, reviews the plan, gets your approval, then executes.
1. GetActiveTodoList Check for an incomplete todo list from a previous session.
If one exists and is pending/in_progress/rejected, resume it.
If not (or user declines), proceed to step 2.
2. CreateTodoList Create a new todo list. Returns a todo_list_id used by
all subsequent calls in this pipeline.
3. AddTask (x N) Add each task with a goal, optional requirements,
notes, context for the subagent, and an order_index.
4. GetTodoList Retrieve and display the full plan. All tasks are shown
with their status: pending, in_progress, completed,
failed, or cancelled.
5. ApproveTodoList Present the plan to the user for y/n approval.
If approved: status becomes approved, tasks can run.
If rejected: status becomes rejected. Rebuild the plan
based on user feedback.
6. ExecuteNextTask Dispatch a subagent to execute the next pending task.
Repeated until all tasks are done or a task fails.
When all tasks complete, the todo list is auto-deleted.
Step-by-Step Walkthrough
Here is what each step does, why it exists, and what the agent sees at each stage.
GetActiveTodoList
Crash recovery gate
Before creating anything new, the agent checks whether an incomplete todo list already exists. This is the crash recovery mechanism: if the previous session was interrupted mid-task, the todo list is still in the database. The agent offers to resume it instead of starting fresh.
# Agent calls:
GetActiveTodoList()
# If an incomplete list exists:
Found incomplete todo list from previous session:
Todo List #3 . "Migrate auth from SQLite to PostgreSQL"
Status: in_progress
Tasks: 2 completed, 3 pending
Resume this todo list? [Y/n]:
If the user says yes, the agent skips steps 2-5 and goes straight to ExecuteNextTask. If no, the old list is cancelled and a new one is created.
CreateTodoList
Allocate a new list
Creates a new todo list in the database and returns a todo_list_id. This ID is used by every subsequent call: AddTask, GetTodoList, ApproveTodoList, and ExecuteNextTask. The list is empty at this point .; no tasks yet.
# Agent calls:
CreateTodoList()
# Returns:
todo_list_id: 42
AddTask
Populate the plan
Called once per task. Each task has five fields. The most important is context .; this is how you pass knowledge to the isolated subagent that will execute the task. Without it, the subagent has no idea what came before or what the broader goal is.
| Field | Required? | Purpose |
|---|---|---|
goal |
Required | Clear description of what needs to be done |
requirements |
Optional | Specific constraints the subagent must follow |
notes |
Optional | Additional helpful context (hints, warnings, tips) |
context |
Optional* | Critical for subagent isolation. Background knowledge about the codebase, architecture decisions, file locations, API contracts .; everything the subagent needs to know since it starts with no conversation history. |
order_index |
Required | Execution order (0, 1, 2, ...). Tasks are executed sequentially in ascending order. |
# Agent adds three tasks:
AddTask(todo_list_id=42, goal="Create a new database adapter for PostgreSQL",
requirements="Must match the existing SQLite adapter interface",
context="The project uses src/db/adapter.py as the base class...",
order_index=0)
AddTask(todo_list_id=42, goal="Update all callers to use the new adapter",
context="Callers found in src/api/routes.py, src/auth/login.py...",
order_index=1)
AddTask(todo_list_id=42, goal="Run tests and fix any regressions",
notes="Use pytest. The test suite is in tests/",
order_index=2)
GetTodoList
Review the plan
Retrieves the complete todo list with all tasks and their statuses. The agent uses this to verify the plan looks correct before asking for approval. All tasks start as pending.
# Agent calls:
GetTodoList(todo_list_id=42)
# Returns:
Todo List #42 . 3 tasks
──────────────────────────────────────────────
[0] pending Create a new database adapter for PostgreSQL
[1] pending Update all callers to use the new adapter
[2] pending Run tests and fix any regressions
ApproveTodoList
User approval gate
Displays the full plan to you and asks for approval. This is the human-in-the-loop checkpoint. No code has been touched yet. If you approve, the todo list status is set to approved and tasks can be executed. If you reject, the agent rebuilds the plan based on your feedback.
# Agent calls:
ApproveTodoList(todo_list_id=42)
# User sees:
Todo List #42 . 3 tasks
──────────────────────────────────────────────
[0] Create a new database adapter for PostgreSQL
[1] Update all callers to use the new adapter
[2] Run tests and fix any regressions
──────────────────────────────────────────────
Approve this plan? (y/n):
If you approve, the plan proceeds to execution. If you reject, the todo list status becomes rejected and the agent must rebuild the plan incorporating your feedback.
ExecuteNextTask
Sequential execution loop
Each call dispatches the next pending task to a subagent. The subagent is an isolated session .; it has no access to the orchestrator's conversation history. It receives the task's goal, requirements, notes, context, and a summary of all previously completed tasks.
The agent calls ExecuteNextTask repeatedly until all tasks are done. Each call dispatches exactly one task. Tasks always execute in order_index order .; never in parallel.
# Agent calls sequentially:
ExecuteNextTask(todo_list_id=42) # dispatches task [0]
ExecuteNextTask(todo_list_id=42) # dispatches task [1]
ExecuteNextTask(todo_list_id=42) # dispatches task [2]
# After the last task completes:
All tasks completed. Todo List #42 has been automatically deleted.
Key Behaviors
The todo list system is designed around a few fundamental principles that govern how tasks are created, executed, and recovered.
Sequential Execution
Tasks always execute one at a time, in order_index order. The agent never parallelises task execution. This is deliberate:
Deterministic ordering
Task 1's output (a new file, a refactored module) is available to task 2's subagent because the file system persists between tasks. This means later tasks can depend on earlier ones.
Predictable failure
If a task fails, it is clear which task caused the failure. The agent does not need to untangle overlapping changes from parallel tasks.
Context carry-forward
Each subagent receives a summary of all previously completed tasks. It knows what was done and what files were changed, even though it starts with a clean context window.
Cost control
Parallel execution would multiply API costs. Sequential execution keeps costs linear with the number of tasks.
Subagent Isolation
Each ExecuteNextTask call spawns a fresh subagent. This subagent:
- Has no access to the orchestrator's conversation history
- Receives only the task's
goal,requirements,notes,context, and a summary of completed tasks - Starts with the same role, tools, and system prompt as the orchestrator
- Has access to the code index, file system, and git repo
- Can create its own nested todo lists (see below)
The context field is critical
Because subagents are isolated, the context field on AddTask is the only way to pass knowledge forward. Without sufficient context, the subagent may produce incorrect or incomplete work. Think of context as a mini-briefing document: include file paths, architecture decisions, API contracts, data structures, and any other details the subagent needs.
Crash Recovery
Todo lists are stored in the SQLite database on disk. If the session is interrupted .; terminal closed, process killed, system crash, or API timeout .; the todo list survives. On the next startup:
Agent calls GetActiveTodoList
At the start of every new session, the agent checks for incomplete todo lists. This is baked into the system prompt as a mandatory first step before creating anything new.
Incomplete list detected
If a todo list with status pending, in_progress, or rejected is found, the agent displays it to the user.
User chooses to resume or discard
If the user says yes, the agent picks up from the next pending task. The completed tasks are listed in the context passed to the subagent. If the user says no, the old list is cancelled and a new one can be created.
$ raggie code .
[INFO] Found incomplete todo list from previous session:
Todo List #17: "Refactor auth middleware and update all callers"
Status: in_progress
Tasks: 2 completed, 3 pending
Completed:
[OK] Extract JWT logic into a separate module
[OK] Write unit tests for the new JWT module
Pending:
[0] Update the login endpoint to use the new JWT module
[1] Update the token refresh endpoint
[2] Run the full test suite and fix regressions
Resume this todo list? [Y/n]: Y
Resuming Todo List #17. Executing next pending task...
The crash recovery pairs with the automatic context handover. If a handover occurs mid-todo-list, the handover document records which tasks are done and which remain. The next session receives both the handover document (as a briefing) and the todo list (as a structured plan), and can resume seamlessly.
Nested Todo Lists
Subagents dispatched by ExecuteNextTask can create their own todo lists for complex subtasks. This allows recursive decomposition of work:
Orchestrator (Depth 0)
Todo List #42: "Migrate auth from SQLite to PostgreSQL"
[0] Create PostgreSQL adapter <-- dispatched to Subagent A
[1] Update all callers <-- dispatched to Subagent B
[2] Run tests and fix regressions <-- dispatched to Subagent C
Subagent A (Depth 1) . working on "Create PostgreSQL adapter"
Todo List #43: "PostgreSQL adapter implementation"
[0] Write the adapter class
[1] Add connection pooling
[2] Write adapter unit tests <-- dispatched to Subagent A1
Subagent A1 (Depth 2) . working on "Write adapter unit tests"
Todo List #44: "Adapter test coverage"
[0] Test connection lifecycle
[1] Test query execution
[2] Test error handling
Depth 3 . MAX DEPTH. No further subagents or todo lists allowed.
Maximum nesting depth. At depth 3, subagent dispatch and todo list creation are blocked.
The effort level (/effort) controls max subagent depth, which also limits nesting. Zen (level 1) still lets subagents create todo lists .; they just cannot nest them deeper. The agent remains fully capable of multi-step planning and execution, just in a linear order.
Nested todo lists are independent database records. If a parent task fails, its nested todo lists survive and can be resumed.
Nested todo lists are transparent to the orchestrator. The orchestrator only sees its own tasks. When ExecuteNextTask dispatches a subagent, the subagent manages its own plan internally. The orchestrator simply waits for the subagent to finish. If the subagent creates a nested todo list, that list is automatically deleted when all its tasks complete .; just like the parent list.
Auto-Deletion
When all tasks in a todo list are completed, the todo list is automatically deleted from the database. This keeps the database clean and prevents stale todo lists from accumulating.
What triggers auto-deletion
ExecuteNextTaskcompletes the last pending task → list is deletedMarkTaskCompleteis called on the last incomplete task → list is deleted- Lists with failed or cancelled tasks are not auto-deleted. They remain in the database for inspection and manual cleanup.
Handover Integration
The todo list system works hand-in-hand with RaggieCode's automatic context handover. When a handover occurs mid-todo-list, the active todo list is preserved and the next session can resume it.
How they work together
Todo list is mid-execution
The agent is partway through a todo list. Some tasks are completed, some are pending. The todo list is in the database with status in_progress.
Context window fills up, handover triggers
The agent generates a handover document. The document includes the todo list ID, which tasks are completed, and which task to execute next. The todo list itself stays in the database .; untouched.
New session starts with handover document
The fresh session receives the handover as part of its system prompt. The document says: "You were working on Todo List #42. Tasks 0 and 1 are done. Execute task 2 next."
Agent calls GetActiveTodoList, finds the existing list
As part of its startup routine, the agent checks for incomplete todo lists. It finds #42, sees that it matches the handover document, and offers to resume. The user confirms, and ExecuteNextTask picks up where it left off.
What the handover document records about todo lists
When a handover occurs during todo list execution, the handover document captures:
- The todo list ID so the next session can find it in the database
- Which tasks have been completed and what they produced (files changed, tests written)
- Which task to execute next (the
exact_next_stepfield) - Any decisions made during the completed tasks that the next task's subagent needs to know
For a deeper dive into the handover mechanism, including the full handover document schema and trigger conditions, see the Agent Handover documentation.
Task Lifecycle
Every task transitions through a fixed set of states. Understanding these states helps you interpret what the agent is doing and diagnose issues.
| Status | Meaning | How it is set |
|---|---|---|
| pending | Task has not been executed yet. It is waiting in the queue. | Set automatically when AddTask is called |
| in_progress | A subagent is currently working on this task. Only one task is in_progress at a time. | Set automatically when ExecuteNextTask picks up the task |
| completed | The subagent finished successfully. The task's changes are committed. | Set automatically when the subagent returns successfully; or manually via MarkTaskComplete |
| failed | The subagent encountered an unrecoverable error. The task cannot be completed as-is. | Set automatically when the subagent crashes; or manually via MarkTaskFailed |
| cancelled | The task is no longer needed and was intentionally skipped. Different from failed. | Set manually via MarkTaskCancelled |
Manual status management
Three tools let you manually adjust task status outside the normal ExecuteNextTask flow:
MarkTaskComplete
Use when a task was finished outside the normal flow, or to correct an incorrectly-failed task.
MarkTaskFailed
Use when a task hit an unrecoverable error and cannot be retried.
MarkTaskCancelled
Use when a task is no longer needed. Distinct from failed: cancelled means intentionally skipped.