Agent to-do list with task dependencies

0
Sign in

Build the task tracker an AI agent uses to plan its work. The question comes in three parts. Each part builds on the one before, so keep earlier behavior working.

Part 1: tasks and statuses

A status is an IntEnum in lifecycle order:

Status Value
BLOCKED 1
READY_TO_EXECUTE 2
IN_PROGRESS 3
SUCCEEDED 4 (terminal)
FAILED 5 (terminal)

A Task has task_id: int, description: str and status. Implement TodoList:

  • add_task(description) -> Task creates a task with a new integer ID. IDs increase with each call. The task starts READY_TO_EXECUTE.
  • get_task(task_id) -> Task | None returns the task, or None for an unknown ID.
  • change_task_status(task_id, new_status) -> bool sets the status and returns True. It returns False and changes nothing when the ID is unknown, the task is already terminal, or new_status is not strictly greater than the current status. Statuses only move forward.

Part 2: dependencies

add_task(description, dependency_ids) takes a list of IDs of tasks that already exist (so the graph has no cycles).

  • A new task is READY_TO_EXECUTE when it has no dependencies or all of them have SUCCEEDED, and BLOCKED otherwise. If one of its dependencies has already FAILED, it starts FAILED.
  • After every successful change_task_status:
    • a BLOCKED task whose dependencies have all SUCCEEDED becomes READY_TO_EXECUTE;
    • a BLOCKED task that depends, directly or through a chain, on a task that just FAILED becomes FAILED.
  • Do not rescan the whole list on each change. Touch only the tasks the change can affect.

Part 3: render for an LLM

render_for_llm() -> str returns the full list (including finished tasks) in a format a language model can parse reliably. Show each task's ID, status, description and dependencies. Choose and justify the format.

Example

t = TodoList()
t.add_task("download dataset")            # 1, READY_TO_EXECUTE
t.add_task("clean rows", [1])             # 2, BLOCKED
t.add_task("train model", [1, 2])         # 3, BLOCKED
t.change_task_status(1, IN_PROGRESS)      # True
t.change_task_status(1, READY_TO_EXECUTE) # False: backward
t.change_task_status(1, SUCCEEDED)        # True; task 2 -> READY_TO_EXECUTE, task 3 stays BLOCKED
t.change_task_status(2, FAILED)           # True; task 3 -> FAILED
t.add_task("write report", [3])           # 4, FAILED (a dependency already failed)

Constraints: up to 10^5 tasks and 2 * 10^5 dependency references in total.

PerplexityTechnical phone screenMediumLast asked May 2026

Sign in to see the worked answer

The full answer, chapter by chapter, with what interviewers expect at each level. Free with an account: one full answer per company.