OpenCode plugin: enhanced TODO tools (dual storage + dependency graph)
Smart TODO management with dependency graphs and priority tracking
Stop juggling tasks in your head โ let the graph show you what to do next!
---
An OpenCode plugin that transforms TODO lists into intelligent task graphs:
- ๐ฏ Dependency tracking โ tasks know what blocks them
- ๐ Visual graph analysis โ see available, blocked, and parallel tasks
- ๐ฅ Priority system โ CRIT | HIGH | MED | LOW with auto-sorting
- ๐๏ธ Hierarchical IDs โ E01-S01-T01 (Epic โ Story โ Task)
- ๐พ Dual storage โ enhanced features + native TUI integration
- ๐ Smart transitions โ auto-promote tasks when conditions are met
---
This is not a problem introduced by this plugin โ it is an existing problem in the default TodoRead / TodoWrite MCP tools that ship with AI coding agents (Claude Code, OpenCode, etc.).
The default TODO tools work through MCP (Model Context Protocol). When the agent calls a TODO tool, two things happen:
1. The MCP tool response is added to conversation history (the agent sees the result)
2. A separate chat message is injected into the conversation as a workaround for UI display
The agent sees both โ the tool result AND the duplicate chat message. Every single TODO call produces double output in the context window:
```
Agent calls: TodoWrite(...)
โ tool response: +800 tokens (MCP result โ agent sees this)
โ chat message: +800 tokens (UI workaround โ agent sees this too)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Total per call: ~1,600 tokens (2x duplication)
In a typical session, an agent calls TODO tools dozens of times โ creating tasks, updating statuses, reading the list, checking what's next. Without cleanup:
``
Call 1: TodoWrite โ +1,600 tokens (tool result + chat duplicate)
Call 2: TodoUpdate โ +1,600 tokens (tool result + chat duplicate)
Call 3: TodoRead โ +1,600 tokens (tool result + chat duplicate)
Call 4: TodoUpdate โ +1,600 tokens (tool result + chat duplicate)
...
Call 20: TodoUpdate โ +1,600 tokens (tool result + chat duplicate)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Total TODO overhead: ~32,000 tokens โ stale duplicated snapshots
Only the last call result matters. The other 19 are stale data sitting in the context window, pushing out useful code, conversation, and reasoning. This is the context leak โ silent, cumulative, and it degrades agent performance as the session progresses.
The problem compounds with larger task lists. A 30-task graph with dependencies can produce 2,000+ tokens per call. With 2x duplication, after 15 updates that's 60,000 tokens of dead weight โ roughly 20-30% of a typical context window, gone.
This plugin solves both problems โ stale accumulation and duplication โ by hooking into experimental.chat.messages.transform to automatically clean up old TODO results before each LLM call:
``
Call 1: usethis_todo_write โ [TODO pruned] + snapshot removed โ cleaned
Call 2: usethis_todo_update โ [TODO pruned] + snapshot removed โ cleaned
Call 3: usethis_todo_read โ [TODO pruned] + snapshot removed โ cleaned
Call 4: usethis_todo_update โ (full graph output) + latest snapshot โ only latest kept
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Total TODO overhead: ~800 tokens โ constant, not cumulative
How it works:
1. Track last call โ after each TODO tool execution, the plugin records the callID in a per-session state[TODO pruned]
2. Transform messages โ before each LLM inference, the plugin scans all messages and replaces old TODO tool outputs with , clearing both output and input fields## TODO
3. Keep latest โ only the most recent TODO call result is preserved in full, giving the agent current state
4. Prune chat snapshots โ the plugin publishes TODO state as user messages for UI display; old snapshot messages are removed entirely from the message list, keeping only the latest one. This eliminates the duplication problem where the agent would otherwise see both the tool result and the chat messagedone
5. Complete cleanup โ when all tasks are marked , even the last result and the last snapshot are pruned โ the TODO list served its purpose and no longer needs context space
Result: TODO tool overhead is O(1) instead of O(n) โ constant ~800 tokens regardless of how many times the agent interacts with the task list. The 2x duplication from the chat workaround is also eliminated.
| Content | Pruned? | Why |
|---------|---------|-----|
| Old tool outputs (todo_write, todo_update, etc.) | Yes | Stale state, agent doesn't need old snapshots |## TODO
| Old tool inputs | Yes | Arguments to stale calls are not useful |
| Old chat snapshot messages | Yes | Duplicate of tool output injected for UI display |## TODO
| Latest tool call output | No | Agent needs current state to make decisions |
| Latest chat snapshot | No | Kept for UI consistency |
| All outputs when all tasks are done | Yes | Work is finished, free the entire context budget |
| | Default TodoRead/TodoWrite | @comfanion/usethis_todo |
|---|---|---|
| Tool result in context | Accumulates forever | Auto-pruned, only latest kept |
| Chat message duplicate | Accumulates forever | Auto-pruned, only latest kept |
| Tokens per call (visible to agent) | ~1,600 (tool + chat duplicate) | ~800 (only latest) |
| After 20 calls | ~32,000 tokens wasted | ~800 tokens constant |
| When all tasks done | Still in context | Fully cleaned up |
| Context overhead | O(n) โ grows with every call | O(1) โ constant |
---
`bash`
npm install @comfanion/usethis_todo
Add to opencode.json:
`json`
{
"plugin": ["@comfanion/usethis_todo"]
}
`javascript`
usethis_todo_write({
todos: [
{
id: "E01-S01-T01",
content: "Setup database schema",
status: "todo",
priority: "HIGH"
},
{
id: "E01-S01-T02",
content: "Create API endpoints",
status: "todo",
priority: "HIGH",
blockedBy: ["E01-S01-T01"] // Waits for T01
}
]
})
Output:
`
TODO Graph [0/2 done, 0 in progress]
All Tasks:
- E01
- E01-S01
- โ ๐ E01-S01-T01: Setup database schema
- โ ๐ E01-S01-T02: Create API endpoints โ E01-S01-T01
Available Now:
- E01
- E01-S01
- โ ๐ E01-S01-T01: Setup database schema
Blocked:
- E01
- E01-S01
- โ ๐ E01-S01-T02: Create API endpoints โ E01-S01-T01
`
---
Tasks can depend on other tasks. The plugin automatically:
- โ
Shows which tasks are available (no blockers)
- โ Shows which tasks are blocked (waiting on others)
- ๐ Resolves transitive dependencies (A โ B โ C)
- โก Identifies parallel tasks (can work on simultaneously)
Example:
`javascript`
usethis_todo_write({
todos: [
{ id: "T01", content: "Design API", status: "done", priority: "HIGH" },
{ id: "T02", content: "Implement API", status: "todo", priority: "HIGH", blockedBy: ["T01"] },
{ id: "T03", content: "Write tests", status: "todo", priority: "MED", blockedBy: ["T02"] },
{ id: "T04", content: "Setup CI/CD", status: "todo", priority: "MED" } // Parallel!
]
})
Graph shows:
- Available: T02 (T01 done), T04 (no blockers)
- Blocked: T03 (waits for T02)
- Parallel: T02 and T04 can be done simultaneously
Four priority levels with visual indicators:
| Priority | Icon | Use Case |
|----------|------|----------|
| CRIT | ๐ด | Production down, security issue |HIGH
| | ๐ | Sprint goal, blocking others |MED
| | ๐ก | Normal work (default) |LOW
| | ๐ข | Nice to have, refactoring |
Tasks are auto-sorted by priority in all views.
Organize tasks in 3 levels:
``
E01-S01-T01
โ โ โโโ Task 01
โ โโโโโโโ Story 01
โโโโโโโโโโโ Epic 01
Benefits:
- ๐ Nested display (epics โ stories โ tasks)
- ๐ Easy filtering (all tasks in E01-S01)
- ๐ Progress tracking per epic/story
``
todo โ in_progress โ ready โ done
โ โ โ โ
โ โ โณ โ
Auto-transitions:
- ready โ done when releases field is setโ
- Blocked tasks show icon (even if status is todo)
Mark tasks as part of releases:
`javascript`
usethis_todo_update({
todos: [{
id: "E01-S01-T01",
status: "ready",
releases: ["E01-S01-T01-ST02"] // Auto-promotes to "done"
}]
})
---
Create or replace entire TODO list.
`javascript`
usethis_todo_write({
todos: [
{
id: "E01-S01-T01", // Required: Hierarchical ID
content: "Task summary", // Required: Short description
description: "Full details...", // Optional: Long description
status: "todo", // Required: todo | in_progress | ready | done
priority: "HIGH", // Required: CRIT | HIGH | MED | LOW
blockedBy: ["E01-S01-T02"], // Optional: Dependency IDs
releases: ["E01-S01", "E01-S01-T02"] // Optional: Release tags
}
]
})
Returns: Full graph analysis
Read all tasks with graph analysis.
`javascript`
usethis_todo_read()
Returns:
- All tasks (nested by epic/story)
- Available tasks (ready to work on)
- Blocked tasks (waiting on dependencies)
- Progress stats (X/Y done, Z in progress)
Get next 5 available tasks (smart view).
`javascript`
usethis_todo_read_five()
Returns:
- Top 5 tasks by priority
- Resolved blockers (what they depend on)
- Missing dependencies (if any)
Perfect for: "What should I work on next?"
Get details for specific task.
`javascript`
usethis_todo_read_by_id({ id: "E01-S01-T01" })
Returns:
- Task details
- Full dependency chain
- Missing dependencies
Update one or more tasks (merge mode).
`javascript`
usethis_todo_update({
todos: [
{
id: "E01-S01-T01",
status: "done" // Only update status, keep other fields
},
{
id: "E01-S01-T02",
status: "in_progress",
priority: "CRIT" // Update multiple fields
}
]
})
Returns: Updated graph
---
`javascript`
// Create sprint tasks
usethis_todo_write({
todos: [
// Epic 01: User Authentication
{ id: "E01-S01-T01", content: "Design auth API", status: "done", priority: "HIGH" },
{ id: "E01-S01-T02", content: "Implement JWT", status: "in_progress", priority: "HIGH", blockedBy: ["E01-S01-T01"] },
{ id: "E01-S01-T03", content: "Add refresh tokens", status: "todo", priority: "MED", blockedBy: ["E01-S01-T02"] },
// Epic 02: User Profile (parallel work)
{ id: "E02-S01-T01", content: "Design profile schema", status: "todo", priority: "MED" },
{ id: "E02-S01-T02", content: "Create profile API", status: "todo", priority: "MED", blockedBy: ["E02-S01-T01"] }
]
})
Graph shows:
- Available: E01-S01-T02 (in progress), E02-S01-T01 (can start)
- Blocked: E01-S01-T03, E02-S01-T02
- Parallel: E01 and E02 can progress simultaneously
`javascript
// Morning: What's next?
usethis_todo_read_five()
// โ Shows top 5 tasks by priority
// Start working
usethis_todo_update({
todos: [{ id: "E01-S01-T02", status: "in_progress" }]
})
// Finish task
usethis_todo_update({
todos: [{ id: "E01-S01-T02", status: "done" }]
})
// Check what unblocked
usethis_todo_read_five()
// โ E01-S01-T03 now available!
`
`javascript
// Critical bug found!
usethis_todo_update({
todos: [{
id: "BUG-001",
content: "Fix login crash",
status: "todo",
priority: "CRIT" // Jumps to top of queue
}]
})
usethis_todo_read_five()
// โ BUG-001 is first (CRIT priority)
`
`javascript
// Mark tasks as completed with linked subtask IDs
usethis_todo_update({
todos: [
{ id: "E01-S01-T01", status: "ready", releases: ["E01-S01-T01-ST01", "E01-S01-T01-ST02"] },
{ id: "E01-S01-T02", status: "ready", releases: ["E01-S01-T02-ST01"] }
]
})
// โ Auto-promotes to "done" when releases set
// Check progress
usethis_todo_read()
// โ Both tasks promoted to "done"
`
`javascript
usethis_todo_write({
todos: [
{ id: "T01", content: "Database schema", status: "done", priority: "HIGH" },
{ id: "T02", content: "API layer", status: "done", priority: "HIGH", blockedBy: ["T01"] },
{ id: "T03", content: "Business logic", status: "todo", priority: "HIGH", blockedBy: ["T02"] },
{ id: "T04", content: "UI components", status: "todo", priority: "MED", blockedBy: ["T03"] },
{ id: "T05", content: "Integration tests", status: "todo", priority: "MED", blockedBy: ["T04"] }
]
})
// Check specific task
usethis_todo_read_by_id({ id: "T05" })
// โ Shows full chain: T05 โ T04 โ T03 โ T02 โ T01
`
---
The plugin writes to two locations:
#### 1. Enhanced Storage (Project-local)
``
.opencode/
session-todo/
{session-id}.json # Full data with dependencies
todo.log # Action log
Contains:
- Full task data (description, blockedBy, releases)
- Timestamps (createdAt, updatedAt)
- All custom fields
#### 2. Native Storage (TUI Integration)
``
~/.local/share/opencode/storage/todo/{session-id}.jsonor
~/Library/Application Support/opencode/storage/todo/{session-id}.json
Contains:
- Simplified format for OpenCode TUI
- Status/priority mapped to native values
- Dependencies shown in content field
Why dual storage?
- โ
Enhanced features (graph, dependencies)
- โ
Native TUI display (sidebar integration)
- โ
Best of both worlds!
---
| Icon | Status | Meaning |
|------|--------|---------|
| โ | todo | Not started |in_progress
| โ | | Working on it |ready
| โณ | | Done, awaiting release |done
| โ | | Completed |cancelled
| โ | | Abandoned |
| โ | (blocked) | Has active blockers |
| Icon | Priority | Color |
|------|----------|-------|
| ๐ด | CRIT | Red |
| ๐ | HIGH | Orange |
| ๐ก | MED | Yellow |
| ๐ข | LOW | Green |
---
Tasks automatically transition when conditions are met:
`javascript
// Task in "ready" status
{ id: "T01", status: "ready", releases: [] }
// Add completed subtask IDs
usethis_todo_update({
todos: [{ id: "T01", releases: ["T01-ST01", "T01-ST02"] }]
})
// โ Auto-promotes to "done"!
`
The plugin resolves entire dependency chains:
`javascript
// T03 depends on T02, T02 depends on T01
usethis_todo_read_by_id({ id: "T03" })
// Shows full chain:
// Blocked By (resolved):
// - T02 (in_progress)
// - T01 (done)
`
Identifies tasks that can be worked on simultaneously:
`javascript`
// Graph analysis shows:
// Parallel groups:
// - [T02, T04, T05] โ Can all start now
// - [T03, T06] โ Can start after T02 done
Warns about broken references:
`javascript
{ id: "T01", blockedBy: ["T99"] } // T99 doesn't exist
usethis_todo_read_by_id({ id: "T01" })
// โ Blocked By missing: T99
`
---
While hierarchical IDs are recommended, you can use any format:
`javascript
// Hierarchical (recommended)
{ id: "E01-S01-T01" } // Epic-Story-Task
// Flat
{ id: "TASK-001" }
// Custom
{ id: "AUTH-LOGIN-JWT" }
`
Note: Hierarchical IDs get nested display, others show flat.
`javascript`
// Get all in-progress tasks
const todos = await usethis_todo_read()
const wip = todos.filter(t => t.status === "in_progress")
`javascriptEpic 01: ${done}/${total} done
// Track epic progress
const todos = await usethis_todo_read()
const epic01 = todos.filter(t => t.id.startsWith("E01-"))
const done = epic01.filter(t => t.status === "done").length
const total = epic01.length
console.log()`
---
All operations are logged to .opencode/todo.log:
``
[2024-01-15T10:30:00.000Z] WRITE: Created/Updated 5 tasks in session abc123
[2024-01-15T10:31:00.000Z] UPDATE: Updated 1 task(s) in session abc123
[2024-01-15T10:32:00.000Z] READ_FIVE: Read next 5 tasks (total ready: 12)
`bashEnhanced storage
cat .opencode/session-todo/{session-id}.json
$3
`javascript
// Check for circular dependencies
usethis_todo_read()
// โ Graph analysis will show if tasks are mutually blocked
``---
| Feature | Simple TODO | usethis_todo |
|---------|-------------|--------------|
| Task list | โ
| โ
|
| Dependencies | โ | โ
|
| Priority sorting | โ | โ
|
| Graph analysis | โ | โ
|
| Parallel detection | โ | โ
|
| Hierarchical IDs | โ | โ
|
| Auto-transitions | โ | โ
|
| Release tracking | โ | โ
|
| Feature | Jira/Asana | usethis_todo |
|---------|------------|--------------|
| In your editor | โ | โ
|
| Offline | โ | โ
|
| Free | โ | โ
|
| Fast | ๐ | โก |
| No context switch | โ | โ
|
| Version controlled | โ | โ
(project-local) |
---
- Storage format: JSON
- Session isolation: Tasks are per-session (multi-session support)
- Dependency resolution: Recursive graph traversal
- Priority sorting: Stable sort (CRIT โ HIGH โ MED โ LOW โ ID)
- Status normalization: Backward-compatible with old formats
- TUI integration: Automatic sync to native storage
---
Found a bug? Have an idea? Open an issue or PR!
---
MIT
---
Made with โค๏ธ by the Comfanion team
---
Work smart, not hard โ let the graph guide you! ๐