All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
asyncpgis now an optional dependency.pip install pydantic-ai-todono longer pulls in a Postgres driver — installpip install 'pydantic-ai-todo[postgres]'to useAsyncPostgresStorage. Users on psycopg3, custom backends, or the in-memory/Redis storage no longer pay the unused-dep cost.AsyncPostgresStorage.initialize()raises anImportErrorwith the install hint if the extra is missing.redisremains a required dependency; a follow-up may give it the same treatment. Resolves #19.
- Batch status updates (
update_todo_statuses). New tool that applies several{todo_id, status}transitions in a single call, so an agent can express one logical workflow step — e.g. mark the current taskcompletedand the next taskin_progress— without chaining separateupdate_todo_statuscalls (and the extraread_todosround-trips they encourage). The batch is all-or-nothing: it is fully validated first (unknown IDs, invalid statuses, and starting a task with incomplete dependencies all abort the batch with no changes applied), then applied, returning a compact summary of the transitions. Available on both sync and async storage and honoursenable_subtasks(theblockedstatus and dependency checks). Adds theTodoStatusUpdateinput model and theUPDATE_TODO_STATUSES_DESCRIPTIONconstant to the public API. Resolves #32.
TodoItem.statusnow defaults to"pending". Creating a plan withwrite_todosno longer requires astatuson every item — a new task ispendingby definition, so the field can be omitted. This removes the extra validation round-trip that agents previously paid when they leftstatusout and had to retry. The field description nudges agents to setstatusexplicitly when restructuring an existing list (so in-progress/completed tasks are preserved). Resolves #29 and #30.
- Docstring hygiene (internal; no behavior change). Converted reStructuredText-style double-backtick inline code in docstrings and comments to single-backtick Markdown (25 occurrences), so it renders correctly under the mkdocstrings Markdown handler. No function-local imports needed hoisting (the package already keeps its imports at module top).
- Documentation accuracy pass. Corrected the installation guide (PostgreSQL
asyncpgand Redisredisare core dependencies, not optional extras), documented the previously-undocumentedAsyncRedisStoragebackend (API reference, storage overview, and connection-lifecycle notes), and fixed the misleading "tasks automatically unblock" claim — ablockedstatus is never auto-cleared;get_available_taskscomputes availability live and excludes blocked tasks. Added an explicit note that task events fire only from async storage backends (the syncTodoStorage/toolset path emits nothing), fixed theread_todossample output to match the real numbered/icon format, replaced hand-copied (and drifted)TodoItem/protocol snippets with mkdocstrings renders, documented the exported*_DESCRIPTION/TODO_SYSTEM_PROMPTconstants, and noted the static-prompt behavior when onlyasync_storageis provided.mkdocs build --strictnow passes with zero warnings.
Pure CI / dependency-bot housekeeping — no source-code changes, no behaviour change since 0.2.2. Consolidates the open Renovate auto-PRs and the rate-limited items from the Dependency Dashboard #24 into a single release so downstream consumers see one bump instead of four.
- CI: bump
actions/checkouttov6acrossci.yml(×3),docs.yml,publish.yml(#23, Renovate auto-PR — folded in here). - CI: bump
docs.ymlPython to3.14(#22, Renovate auto-PR — folded in here). - CI: bump
astral-sh/setup-uvtov8.1.0acrossci.yml(×3) andpublish.yml— from Dashboard #24. Pinned to the specific patch becauseastral-sh/setup-uvdoes not maintain a rollingv8tag (onlyv8.0.0/v8.1.0;v7and earlier do have rolling majors). - CI: bump
actions/setup-pythontov6indocs.yml— from Dashboard #24;v6has a rolling tag so plain@v6is used.
The ci.yml test matrix (["3.10", "3.13"] and ["3.10", "3.11", "3.12", "3.13"]) is unchanged in this release.
AsyncRedisStorage— third storage backend, Redis-based (#18 by @shamalgithub). Stores each session's todos in a Redis Hash (one field per todo, JSON-serialized) with a companion List for insertion order. Supports either URL-based construction (AsyncRedisStorage(url="redis://...", session_id=...)) or injection of an existingredis.asyncio.Redisclient (AsyncRedisStorage(client=..., session_id=...)). Session-scoped keys give multi-tenancy out of the box, the event emitter integration mirrors the other backends (CREATED, UPDATED, STATUS_CHANGED, COMPLETED, DELETED), andcreate_storage("redis", ...)plus a new overload land the factory shape. Addsredis>=7.4.0as a dependency and exportsAsyncRedisStoragefrom the package root.
AsyncRedisStorage.remove_todois now atomic. The previous two-round-trip implementation (hdelthenlrem) could leave the order list pointing at a deleted hash entry if the connection dropped between the two; both ops now go through a single pipeline so they cannot diverge.AsyncRedisStorageis Redis Cluster-safe._hash_keyand_order_keynow share a{session_id}hash-tag (todos:{user-123}/todos:{user-123}:order) so both keys route to the same Cluster slot. Without it the multi-key pipelines inset_todos/add_todo/remove_todowould fail withCROSSSLOTunder clustered deployments. No-op on single-node Redis.AsyncRedisStorage.initialize()is now idempotent. A second call is a no-op instead of issuing anotherPING..gitignorecleanup — fixed an "Ingore" typo, trimmed trailing whitespace, and added the missing final newline introduced in #18.
- Bump minimum
pydantic-ai-slimto>=1.74.0for compatibility with asyncget_instructionson toolsets
TodoCapability— new pydantic-ai capability that bundles todo tools + dynamic system prompt into a single plug-and-play unit. This is now the recommended way to add todo support:from pydantic_ai import Agent from pydantic_ai_todo import TodoCapability agent = Agent("openai:gpt-4.1", capabilities=[TodoCapability()])
- Registers all tools automatically (
read_todos,write_todos,add_todo,update_todo_status,remove_todo, and subtask tools when enabled) - Injects dynamic system prompt showing current todo state — no manual
get_todo_system_prompt()wiring needed - Supports AgentSpec YAML serialization (
Agent.from_file("agent.yaml")) - Accepts all
create_todo_toolset()params:storage,async_storage,enable_subtasks,descriptions
- Registers all tools automatically (
read_todosloop when all tasks completed — when all tasks are done,read_todosnow returns an explicit "All tasks are completed. Do NOT call read_todos again" message. Previously the model would keep callingread_todosin a loop because the response didn't signal that no further action was needed. (#14, reported by @ilayu-blip)
- Documentation rewritten for capabilities-first approach — README, quick start, examples, and concept pages now lead with
TodoCapability. The toolset API (create_todo_toolset()) is documented as a lower-level alternative for users who need more control.
- Custom tool descriptions —
create_todo_toolset()now acceptsdescriptions: dict[str, str] | Noneparameter to override any tool's built-in description. Enables customizing agent behavior without forking the library (#11)
- Enriched all tool description constants — All 9 description constants (
TODO_TOOL_DESCRIPTION,TODO_SYSTEM_PROMPT,READ_TODO_DESCRIPTION,ADD_TODO_DESCRIPTION,UPDATE_TODO_STATUS_DESCRIPTION,REMOVE_TODO_DESCRIPTION,ADD_SUBTASK_DESCRIPTION,SET_DEPENDENCY_DESCRIPTION,GET_AVAILABLE_TASKS_DESCRIPTION) rewritten with detailed "When to Use" / "When NOT to Use" sections, status workflow documentation, parameter explanations, and practical tips. Follows the Claude Code / deepagents pattern of putting comprehensive guidance directly in tool descriptions. - Expanded
TODO_SYSTEM_PROMPT— Tool listing now includes brief usage guidance for each tool. Added "Task Workflow" section with numbered steps.
-
System prompt now includes todo IDs:
get_todo_system_prompt()andget_todo_system_prompt_async()now render todos as- [x] [abc123ef] Task contentinstead of- [x] Task content. Previously, agents could see their todos in the system prompt but had no way to know the IDs needed byupdate_todo_statusandremove_todowithout first callingread_todos. (#9 by @pedro-at-noxus) -
Improved
active_formparameter descriptions: Added concrete transformation examples (e.g., "Fix the login bug" → "Fixing the login bug") toadd_todoandadd_subtasktool descriptions and docstrings. Some models previously asked the user for this value instead of generating it from the task content. (#9 by @pedro-at-noxus)
- Event Subscription Patterns: New documentation section covering:
- Single event, multiple subscribers
- Single subscriber, multiple events via stacked decorators
- Conditional event handling
- Dynamic registration/unregistration at runtime
- Class-based event handlers for organizing related logic
- Cycle Detection Deep Dive: Expanded documentation explaining:
- DFS algorithm for cycle detection
- Self-dependency, direct cycle, and transitive cycle cases
- Diamond dependencies (allowed)
- Practical examples for each case
- Multi-Tenancy Example: New guide for per-user task isolation in web applications
- Migration Guide: New guide for transitioning from memory to PostgreSQL storage
- Navigation: Updated docs nav to include multi-tenancy and migration guide
- Lightweight dependency: Replaced
pydantic-aiwithpydantic-ai-slimto reduce install footprint — pulls in only the core modules needed by the toolset - Removed CLI: Dropped the CLI entry point to keep the package focused as a library-only toolset
- Missing Exports: Added
UPDATE_TODO_STATUS_DESCRIPTIONandREMOVE_TODO_DESCRIPTIONconstants to public API
- Documentation: Fixed
create_todo_toolsetsignature - added missingidparameter and corrected return type toFunctionToolset[Any]
- Full Documentation Site: MkDocs Material documentation matching pydantic-deep style
- Concepts: Toolset, Storage, Types
- Advanced: Subtasks & Dependencies, Event System
- Examples: Basic Usage, Async Storage, PostgreSQL, Subtasks, Events
- API Reference: Auto-generated from docstrings
- GitHub Actions Workflow: Auto-deploy docs to GitHub Pages on push to main
- Custom Styling: Pink theme with Inter/JetBrains Mono fonts
- README: Complete rewrite with centered header, badges, Use Cases table, and vstorm-co branding
- Documentation accuracy: Added missing "blocked" status to all status lists
- Documentation accuracy: Fixed
previous_statetype fromstr | NonetoTodo | None
- Todo IDs: Auto-generated 8-char hex IDs for todos (
uuid4().hex[:8]) - Atomic CRUD Operations:
add_todo(content, active_form)- Add single taskupdate_todo_status(todo_id, status)- Update task status by IDremove_todo(todo_id)- Delete task by ID
- Async Storage Protocol:
AsyncTodoStorageProtocol- Interface for async storage backendsAsyncMemoryStorage- In-memory async implementationcreate_storage(backend)- Factory function for storage backendsget_todo_system_prompt_async()- Async version of system prompt generator
- Task Hierarchy (opt-in via
enable_subtasks=True):parent_idanddepends_onfields on Todo modeladd_subtask(parent_id, content, active_form)- Create child tasksset_dependency(todo_id, depends_on_id)- Link tasks with cycle detectionget_available_tasks()- List tasks ready to work onblockedstatus for tasks with incomplete dependencies- Hierarchical tree view in
read_todos
- Event System:
TodoEventTypeenum: CREATED, UPDATED, STATUS_CHANGED, DELETED, COMPLETEDTodoEventmodel with event_type, todo, timestamp, previous_stateTodoEventEmitterclass withon/off/emitmethods- Convenience decorators:
on_created,on_completed,on_status_changed,on_updated,on_deleted - Event integration with
AsyncMemoryStorageandAsyncPostgresStorage
- PostgreSQL Storage Backend:
AsyncPostgresStorage- Full PostgreSQL implementation- Session-based multi-tenancy via
session_idparameter - Support for connection string or existing
asyncpg.Pool - Auto table creation on
initialize() asyncpg>=0.29.0as required dependency
read_todosoutput now includes todo IDs:"1. [ ] [id] content"TODO_SYSTEM_PROMPTupdated to document all available toolscreate_storage()now supports"postgres"backend
__version__now dynamically reads from package metadata (pyproject.toml) viaimportlib.metadata