From bf50983d768a3c12bb4fdb257451e6e725f1d6f1 Mon Sep 17 00:00:00 2001 From: Rudy Celekli Date: Mon, 12 Oct 2026 21:15:51 -0400 Subject: [PATCH] fix(agent): reject ambiguous duplicate todo identities --- apps/agent/src/todos.py | 5 +++ .../tests/test_todo_unique_ids_regression.py | 37 +++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 apps/agent/tests/test_todo_unique_ids_regression.py diff --git a/apps/agent/src/todos.py b/apps/agent/src/todos.py index b647fee..d7d6137 100644 --- a/apps/agent/src/todos.py +++ b/apps/agent/src/todos.py @@ -20,6 +20,11 @@ def manage_todos(todos: list[Todo], runtime: ToolRuntime) -> Command: """ Manage the current todos. """ + # Validate supplied IDs before assigning any missing IDs or updating state. + supplied_ids = [todo["id"] for todo in todos if todo.get("id")] + if len(supplied_ids) != len(set(supplied_ids)): + raise ValueError("Todo IDs must be unique; give each item a distinct ID.") + # Ensure all todos have IDs that are unique for todo in todos: if "id" not in todo or not todo["id"]: diff --git a/apps/agent/tests/test_todo_unique_ids_regression.py b/apps/agent/tests/test_todo_unique_ids_regression.py new file mode 100644 index 0000000..3d70eb3 --- /dev/null +++ b/apps/agent/tests/test_todo_unique_ids_regression.py @@ -0,0 +1,37 @@ +from copy import deepcopy + +import pytest +from langchain.tools import ToolRuntime + +from src.todos import manage_todos + + +def runtime(): + return ToolRuntime(state={"messages": [], "todos": []}, context=None, config={}, + stream_writer=lambda _: None, tool_call_id="todo-call", store=None) + + +def todo(id, title): + return {"id": id, "title": title, "description": "", "emoji": "", "status": "pending"} + + +def test_duplicate_supplied_ids_are_rejected_before_state_update(): + items = [todo("", "New"), todo("same", "First"), todo("same", "Second")] + original = deepcopy(items) + with pytest.raises(ValueError, match="unique"): + manage_todos.func(items, runtime()) + assert items == original + + +def test_unique_supplied_ids_are_stable_in_state_update(): + items = [todo("one", "First"), todo("two", "Second")] + result = manage_todos.func(items, runtime()) + assert result.update["todos"] == items + assert result.update["messages"][0].tool_call_id == "todo-call" + + +def test_new_todos_receive_distinct_nonempty_ids(): + result = manage_todos.func([todo("", "First"), todo("", "Second")], runtime()) + ids = [item["id"] for item in result.update["todos"]] + assert all(ids) + assert len(set(ids)) == 2