Documentation
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

  1. Apply migration 031_plan_todo_dependencies.sql (run supabase db reset or apply manually)
  2. Verify the plan_todo_deps table exists with columns: id, plan_id, dependent_id, blocker_id, created_at
  3. Verify dependencies_enabled column exists on both agent_plan_config and runner_plan_config

1. Config Toggle

Test: Dependencies toggle appears only when plans are enabled

  1. Go to Agent Settings
  2. Verify "Enable task dependencies" toggle is not visible when plans are disabled
  3. Enable plans
  4. Verify "Enable task dependencies" toggle appears below "Remind on turn end"
  5. Toggle it on, save, refresh — verify it persists
  6. Toggle it off, save, refresh — verify it persists

Test: Runner settings mirror agent settings

  1. 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

  1. Mark todo A as done
  2. GET /api/plans/:planId/ready
  3. Expected: todo B now appears (its only blocker is done)

Test: Skipping a blocker counts as resolved

  1. Mark todo A as skipped
  2. GET /api/plans/:planId/ready
  3. 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

  1. Create agent with plans enabled, dependencies disabled
  2. Start a conversation — verify only 5 plan tools appear (plan_create, plan_update, plan_get, todo_add, todo_update)
  3. Enable dependencies on the agent
  4. Start a new conversation — verify 8 tools appear (+ todo_add_deps, todo_remove_dep, todo_ready)

Test: Agent can use todo_ready

  1. Agent creates a plan with todos and dependencies
  2. Agent calls todo_ready — should get a formatted list of unblocked todos
  3. Agent completes a blocker todo, calls todo_ready again — dependent should now appear

6. Chat Context

Test: Plan context shows blocked annotations

  1. Enable dependencies, create a plan with deps via the API or agent tools
  2. Send a message to the agent
  3. 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

  1. Disable dependencies on the agent
  2. Send a message
  3. Verify plan context does not include blocked annotations or ready count

7. Plan Panel UI (SDK Component)

Test: Blocked todos show lock indicator

  1. Render PlanPanel with a plan that has deps
  2. Verify blocked todos show a lock icon and "Blocked by N tasks" label
  3. Verify blocked todos have reduced opacity (0.5)

Test: Resolved blockers remove indicator

  1. Mark all blockers as done
  2. Re-render — verify the lock indicator is gone and opacity is restored

Test: No indicators when no deps

  1. Render PlanPanel with a plan that has no deps array (or empty)
  2. Verify rendering is identical to before this feature

8. Cascade Delete

Test: Deleting a blocker todo removes its dep edges

  1. Create todos A, B with B depending on A
  2. Delete todo A
  3. GET /api/plans/:planId/todos/:todoBId/deps
  4. Expected: empty blockers (cascade deleted)
  5. GET /api/plans/:planId/ready — todo B should now be ready

Test: Deleting a plan cascades everything

  1. Create plan with todos and deps
  2. Delete the plan
  3. Verify plan_todo_deps rows are gone (no orphans)

9. Edge Cases

ScenarioExpected
Duplicate edge insertionShould 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 ID400 TODO_NOT_IN_PLAN
Marking todo in_progress with pending blockersAllowed (deps don't prevent status changes, they're informational)
Plan with 0 todosReady endpoint returns empty array
Bulk blockerIds with one invalidEntire request rejected before any inserts
Docs last updated May 11, 2026