../../x can never escape, and ordinary names keep the exact filename they always had.
Quick Start
1
Let an agent name what it saves
2
Any name is confined
How It Works
Only characters that could change the path are percent-encoded:%, /, \, :, *, ?, ", <, >, |, control characters, and a leading .. Everything else — including spaces and non-ASCII — is kept as-is.
list_todos() decodes filenames back to the original names, so whatever it returns works with load_todo() and delete_todo().
Which APIs use it
You can use the same helper for your own storage:
Legacy compatibility: todo files written before PraisonAI PR #6239 with plain names are still found by
load_todo and delete_todo — no migration needed.Common Patterns
Safe LLM-named plans
Safe LLM-named plans
Non-ASCII todo lists
Non-ASCII todo lists
Best Practices
Pass names through unchanged
Pass names through unchanged
Don’t pre-sanitise names yourself —
PlanStorage already confines them, and extra mangling breaks the round-trip from list_todos().Use list_todos() for names
Use list_todos() for names
Always use names returned by
list_todos() rather than reading filenames from disk; the listed form is the one load_todo() and delete_todo() expect.Use safe_join for your own files
Use safe_join for your own files
When your own tool writes a file named by an LLM or user, build the path with
safe_join(base, name, suffix) instead of os.path.join.Related
Planning
Plan-then-execute agents
Todo Planning
Agents that track todo lists
Session Store
Collision-safe session filenames
Async Bridge
Shared sync↔async bridge

