Guides
QA: Plan Todo Dependencies
Testing guidelines for the plan todo dependencies feature — blocking edges, cycle detection, ready queries, and UI indicators.
This document covers testing guidelines for the plan todo dependency feature, which adds blocking relationships between todos in a plan.
Prerequisites
- Apply migration
031_plan_todo_dependencies.sql(runsupabase db resetor apply manually) - Verify the
plan_todo_depstable exists with columns:id,plan_id,dependent_id,blocker_id,created_at - Verify
dependencies_enabledcolumn exists on bothagent_plan_configandrunner_plan_config
1. Config Toggle
Test: Dependencies toggle appears only when plans are enabled
- Go to Agent Settings
- Verify "Enable task dependencies" toggle is not visible when plans are disabled
- Enable plans
- Verify "Enable task dependencies" toggle appears below "Remind on turn end"
- Toggle it on, save, refresh — verify it persists
- Toggle it off, save, refresh — verify it persists
Test: Runner settings mirror agent settings
- Repeat the above on a Runner's settings page
2. API: Dependency CRUD
Test: Add dependencies
# Create a plan with 3 todos, then add a dep: todoB blocked by todoA
POST /api/plans/:planId/todos/:todoBId/deps
{ "blockerIds": ["<todoA-id>"] }
# Expected: 201 with array of TodoDep objects
Test: Get dependencies
GET /api/plans/:planId/todos/:todoBId/deps
# Expected: { "blockers": [{ dependent_id: todoBId, blocker_id: todoAId }], "dependents": [] }
Test: Remove a dependency
DELETE /api/plans/:planId/todos/:todoBId/deps
{ "blockerId": "<todoA-id>" }
# Expected: { "ok": true }
# Verify: GET deps returns empty blockers
3. Cycle Detection
Test: Self-dependency rejected
POST /api/plans/:planId/todos/:todoAId/deps
{ "blockerIds": ["<todoA-id>"] }
# Expected: 400 { "error": "SELF_DEPENDENCY" }
Test: Direct cycle rejected
Given: A -> B (B depends on A)
POST /api/plans/:planId/todos/:todoAId/deps
{ "blockerIds": ["<todoB-id>"] }
# Expected: 400 { "error": "CYCLE_DETECTED" }
Test: Transitive cycle rejected
Given: A -> B -> C (C depends on B, B depends on A)
POST /api/plans/:planId/todos/:todoAId/deps
{ "blockerIds": ["<todoC-id>"] }
# Expected: 400 { "error": "CYCLE_DETECTED" }
Test: Valid non-cyclic diamond allowed
Given: A -> B, A -> C
POST /api/plans/:planId/todos/:todoDId/deps
{ "blockerIds": ["<todoB-id>", "<todoC-id>"] }
# Expected: 200, D depends on both B and C (valid DAG)
4. Ready Query
Test: Ready endpoint returns unblocked todos
Given plan with todos A, B, C where B depends on A and C depends on B:
GET /api/plans/:planId/ready
# Expected: Only todo A (it has no blockers)
Test: Completing a blocker unblocks dependents
- Mark todo A as
done - GET
/api/plans/:planId/ready - Expected: todo B now appears (its only blocker is done)
Test: Skipping a blocker counts as resolved
- Mark todo A as
skipped - GET
/api/plans/:planId/ready - Expected: todo B appears (skipped satisfies the dependency)
Test: Todos with no deps are always ready
A todo with zero dependency edges and status pending should always appear in the ready list.
5. Runtime Tools (Agent-side)
Test: Dependency tools only appear when enabled
- Create agent with plans enabled, dependencies disabled
- Start a conversation — verify only 5 plan tools appear (plan_create, plan_update, plan_get, todo_add, todo_update)
- Enable dependencies on the agent
- Start a new conversation — verify 8 tools appear (+ todo_add_deps, todo_remove_dep, todo_ready)
Test: Agent can use todo_ready
- Agent creates a plan with todos and dependencies
- Agent calls
todo_ready— should get a formatted list of unblocked todos - Agent completes a blocker todo, calls
todo_readyagain — dependent should now appear
6. Chat Context
Test: Plan context shows blocked annotations
- Enable dependencies, create a plan with deps via the API or agent tools
- Send a message to the agent
- In the system context (check runtime logs), verify the plan context includes:
← blocked by: <blocker content>on blocked todos- Ready count in summary: e.g.,
"3/10 todos done, 2 ready"
Test: No annotations when dependencies disabled
- Disable dependencies on the agent
- Send a message
- Verify plan context does not include blocked annotations or ready count
7. Plan Panel UI (SDK Component)
Test: Blocked todos show lock indicator
- Render
PlanPanelwith a plan that hasdeps - Verify blocked todos show a lock icon and "Blocked by N tasks" label
- Verify blocked todos have reduced opacity (0.5)
Test: Resolved blockers remove indicator
- Mark all blockers as
done - Re-render — verify the lock indicator is gone and opacity is restored
Test: No indicators when no deps
- Render
PlanPanelwith a plan that has nodepsarray (or empty) - Verify rendering is identical to before this feature
8. Cascade Delete
Test: Deleting a blocker todo removes its dep edges
- Create todos A, B with B depending on A
- Delete todo A
- GET
/api/plans/:planId/todos/:todoBId/deps - Expected: empty blockers (cascade deleted)
- GET
/api/plans/:planId/ready— todo B should now be ready
Test: Deleting a plan cascades everything
- Create plan with todos and deps
- Delete the plan
- Verify
plan_todo_depsrows are gone (no orphans)
9. Edge Cases
| Scenario | Expected |
|---|---|
| Duplicate edge insertion | Should be idempotent (upsert with ignoreDuplicates) |
| Cross-plan dep attempt (via raw SQL) | Rejected by API validation — both todos must share same plan_id |
| Dep on non-existent todo ID | 400 TODO_NOT_IN_PLAN |
Marking todo in_progress with pending blockers | Allowed (deps don't prevent status changes, they're informational) |
| Plan with 0 todos | Ready endpoint returns empty array |
| Bulk blockerIds with one invalid | Entire request rejected before any inserts |