Skip to content

fix(todo): accept the TodoItem shape todo_write declares - #29

Open
eastagiletracker wants to merge 1 commit into
aitomatic:developfrom
eastagiletracker:agile-board/todo-write-accepts-declared-todoitem
Open

fix(todo): accept the TodoItem shape todo_write declares#29
eastagiletracker wants to merge 1 commit into
aitomatic:developfrom
eastagiletracker:agile-board/todo-write-accepts-declared-todoitem

Conversation

@eastagiletracker

Copy link
Copy Markdown

This PR proposes a fix for ToDoResource.todo_write(), which rejects the TodoItem type its own signature declares. We include this PR work along with a full history of your repo at https://eastagiletracker.com/projects/252. You can sign in with your GitHub ID to claim ownership of the project.

The defect

todo_write() is annotated todos: list[TodoItem], but its validation and rendering index every item as a mapping (if "content" not in todo, todo["status"], todo['activeForm']). Passing the very model the signature declares does not raise — it silently returns a false validation error, because BaseModel.__iter__ yields (name, value) tuples, so "content" not in todo is always true. Passing an attribute-style item, such as the TodoItem dataclass in dana.core.runtime.protocols, raises TypeError instead.

Reproduced on develop at acb72ec:

import asyncio
from dana.core.resource.todo_resource import ToDoResource, TodoItem as Model
from dana.core.runtime import TodoItem as Dataclass

r = ToDoResource(resource_id="todo")
fields = dict(content="Run tests", status="pending", activeForm="Running tests")

print(asyncio.run(r.todo_write(todos=[fields])))                    # renders the list
print(asyncio.run(r.todo_write(todos=[Model(**fields)])))           # declared type
print(asyncio.run(r.todo_write(todos=[Dataclass(content="Run tests", status="pending")])))
Todo List: ...                                        # dict: works
Error: Todo item 0 missing 'content' field            # TodoItem: false error
TypeError: argument of type 'TodoItem' is not iterable # dataclass: crash

To be precise about the blast radius: the tool-call path decodes JSON to dicts, so agents driving todo_write through an LLM are unaffected today. What breaks is calling the method directly on its published signature — ToDoResource is exported from dana.core.resource and attached by DanaCodingAgent and the explore agent — and any caller holding the runtime TodoItem dataclass, which crashes outright.

The change

_as_mapping() normalizes each item to a dict before validation, accepting mappings, pydantic models and attribute objects. This mirrors the handling already in thought_logger._display_todo_list, which comments "Handle both TodoItem objects and dicts" — the same two shapes, reconciled in one more place. Dict callers, including the decoded tool-call path, are byte-for-byte unaffected, and get_todos() now genuinely returns the list[dict] it is annotated to return whatever shape went in. An item that is genuinely missing a field, like the two-field runtime dataclass, now gets the intended missing 'activeForm' field message rather than a TypeError.

Verification, with tests/unit/test_todo_resource.py added — this resource had no direct tests before:

  • With the change reverted, 8 of the 12 new tests fail and the 4 dict-path tests still pass, which is what pins the fix to the reported shapes rather than to the suite at large.
  • uv run pytest tests/ -m "not live and not deep" goes from 1898 passed / 38 skipped on a clean develop to 1910 passed / 38 skipped here — the 12 new tests, no new failures and no change in what is skipped.
  • uv run ruff check dana/ passes, as it does on the baseline.

Covered: each accepted shape, all three rendering identically for the same todo, mixed shapes in one call, stored state, missing fields, invalid status, the error index in a mixed list, and the empty list.

How this was managed

We tracked this work on a board imported from this repository's own issues and pull requests (28 stories), so the fix has a story you can read start to finish: todo_write rejects the TodoItem type its own signature declares, on the Dana Runtime board.

board

If you'd rather not receive contributions like this, reply no-more-prs on this pull request and we won't open any further ones on your repositories.


Lawrence W. Sinclair
CEO / East Agile
linkedin.com/in/lwsinclair/
eastagile.com

todo_write is annotated `todos: list[TodoItem]`, but its validation and
rendering index every item as a mapping. Passing the declared model
returns "Error: Todo item 0 missing 'content' field", because
BaseModel.__iter__ yields (name, value) tuples rather than field names,
and passing an attribute-style item raises TypeError.

Normalize each item to a dict before validation, accepting mappings,
pydantic models and attribute objects — mirroring the handling already
used in thought_logger._display_todo_list. Dict callers, including the
decoded tool-call path, are unaffected.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant