Comprehensive documentation covering architecture, the development journey, issues encountered, and lessons learned.
- Project Overview
- Architecture
- The Journey: Issues & Fixes
- Reminder System Deep Dive
- OpenClaw Integration
- Timezone Handling
- Current State
- Lessons Learned
TaskAgent is a self-hosted task management system designed as an alternative to Todoist, built specifically for integration with OpenClaw β an AI agent platform. It provides:
- Unlimited reminders (Todoist's free tier caps at 5)
- AI-powered scheduling based on work routine, holidays, and comp-offs
- Dual interfaces: Flask REST API + JSON-RPC stdio mode for OpenClaw tool calls
- System cron-based reminders for reliability (no dependency on external services)
The project runs on a GCP e2-standard-2 instance in asia-south1, Ubuntu 22.04, with the instance set to auto-delete on April 1, 2026.
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β OpenClaw Agent β
β β
β User says: "remind me at 6pm to take socks" β
β β β
β βΌ β
β Agent (LLM) parses intent β extracts time, message, type β
β β β
β ββββββββββββ΄βββββββββββ β
β βΌ βΌ β
β ββββββββββββ ββββββββββββββββ β
β β API Mode β β Stdio Mode β β
β β (Flask) β β (JSON-RPC) β β
β ββββββ¬ββββββ ββββββββ¬ββββββββ β
β β β β
β ββββββββββββ¬βββββββββββ β
β βΌ β
β βββββββββββββββββββββββββββββββββββββββ β
β β TaskManager Core β β
β β - Task CRUD (task_manager.py) β β
β β - AI Scheduling (scheduler.py) β β
β β - Tool Definitions (tools.py) β β
β β - JSON persistence (my_tasks.json) β β
β βββββββββββββββββββββββββββββββββββββββ β
β β β
β βΌ β
β βββββββββββββββββββββββββββββββββββββββ β
β β Reminder System β β
β β System cron β scripts/ β β
β β reminder-runner.py β Telegram β β
β βββββββββββββββββββββββββββββββββββββββ β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
| File | Purpose |
|---|---|
main.py |
Entry point β Flask API + stdio JSON-RPC mode |
src/task_manager.py |
Core Task/Reminder dataclasses, TaskManager class |
src/tools.py |
OpenClaw tool definitions (TaskTools class) |
src/scheduler.py |
AI scheduling (AIScheduler, SchedulerConfig) |
src/constants.py |
Centralized defaults (priorities, durations) |
src/api_utils.py |
Response formatting helpers |
src/logging_utils.py |
Logging setup |
config.json |
Work routine, holidays, comp-offs |
openclaw_skill.yaml |
OpenClaw skill registration (tool schemas) |
The Problem: OpenClaw's agent kept getting stuck in reasoning loops when asked to set reminders. When a user said "Set a reminder at 9:30 PM to wash the socks", the agent would:
- Invent a
set_remindertool that didn't exist in its available tools - Confuse
openclaw cron add(native cron, unreliable) with system cron - Convert IST to UTC unnecessarily ("21:30 IST = 16:00 UTC") causing further confusion
- Loop indefinitely without ever executing anything
Example of the hallucination loop:
Reasoning: "I need to use the set_reminder tool..."
(NOTE: set_reminder doesn't exist in available tools)
Reasoning: "According to MEMORY.md, use --at '<ISO timestamp>' --delete-after-run"
(NOTE: This references openclaw native cron flags that don't work)
Reasoning: "Actually, I should use the task_manager API..."
(NOTE: API wasn't accessible to the agent)
Root Causes Identified:
MEMORY.mdhad conflicting rules (openclaw cron flags alongside system cron rules)- No clear, step-by-step recipe the agent could follow without reasoning
- The
task_manager'sset_remindermethod wrote directly to system crontab instead of the workspace.crontabfile, causing desync - The
reminder_cron.pyscript (generated by task_manager) only calledprint()β cron discards stdout, so reminders were silently lost
Fixes Applied:
- Created
scripts/reminder-runner.pyβ universal one-time reminder script that sends via Telegram and self-cleans - Fixed
task_manager/src/tools.pyβset_reminder()now writes to workspace.crontaband syncs - Fixed
_create_reminder_cron_script()β now sends via Telegram instead of just printing - Rewrote
MEMORY.mdwith unambiguous step-by-step instructions - Added
TZ=Asia/Kolkatato/etc/environmentfor explicit timezone enforcement
After fixing the reminder system, a full audit of the task_manager/ codebase revealed 15 bugs, 9 of which would crash at runtime.
1. TaskManager.complete_task() return value mismatch
complete_task()returnedbool(True/False)tools.pytreated the return as aTaskobject, accessing.id,.title- Impact: Every API call to complete a task would crash with
AttributeError: 'bool' object has no attribute 'id' - Fix: Get task first, then call complete
2. TaskManager.batch_create_tasks() didn't exist
tools.pycalledself.task_manager.batch_create_tasks()but the method was never implemented- Impact: Batch task creation via API or stdio would crash
- Fix: Added method to
TaskManager
3. Task dataclass missing scheduled_time field
scheduler.pyaccessedtask.scheduled_timebut the field didn't exist- Impact:
get_daily_schedule()would crash withAttributeError - Fix: Added
scheduled_time: Optional[datetime]to Task dataclass
4. TaskManager.get_overdue_tasks() didn't exist
scheduler.pycalledself.task_manager.get_overdue_tasks()but onlyget_tasks(overdue=True)existed- Impact:
suggest_schedule()would crash - Fix: Changed to
get_tasks(overdue=True)
5. task.priority.name on a string
scheduler.pyandmain.pyusedtask.priority.namebutpriorityis stored as a string ("MEDIUM"), not an enum- Impact: Schedule endpoints would return
AttributeError: 'str' object has no attribute 'name' - Fix: Changed to
str(task.priority)
6. tools/list stdio handler wrong response shape
- Handler returned
list(tools.values())which gave[[...tool_list...], count]instead of the tools array - Impact: OpenClaw couldn't discover available tools via stdio
- Fix: Changed to
tools_data.get("tools", [])
7. api_list_tasks ignored show_completed parameter
- The API accepted
show_completedquery param but never passed it tolist_tasks() - Impact: Users couldn't filter completed tasks via API
- Fix: Added param extraction and passing
8. api_get_schedule ignored date parameter and crashed on priority
- The endpoint accepted
datebut always useddue_today=True - Used
task.priority.namewhich crashes on strings - Impact: Schedule for specific dates impossible, current-date schedule crashed
- Fix: Added date filtering + string priority
9. Task dataclass missing reminders field
tools.pyaccessedtask.reminderswhich didn't exist- Impact:
get_task_details()would crash - Fix: Added
reminders: List[Dict]field
10. Default storage path mismatch
TaskManager.__init__defaulted topackage_tasks.jsonmain.pydefaulted tomy_tasks.json- Fix: Aligned both to
my_tasks.json
11. to_dict() serialization incomplete
- Missing
recurrence,estimated_duration,scheduled_time,reminders - Fix: Added all fields
12. test_logging.py β 2 tests never discovered
test_tools_logger_existsandtest_task_manager_logger_existswere indented inside another test method (nested functions)- pytest never discovered or ran them
- Fix: De-indented to class-level methods
13. Unused dependencies β Removed apscheduler and pytz from requirements.txt (never imported)
14. Outdated holidays β Updated config.json holidays from 2024 to 2026
15. YAML endpoint mismatches β Fixed CompleteTask and SetReminder endpoints in openclaw_skill.yaml to match actual Flask routes
All 15 tests pass (including the 2 previously broken tests):
tests/test_api.py::test_api_create_and_list PASSED
tests/test_logging.py::TestLoggingConfiguration (6 tests) PASSED
tests/test_logging.py::TestLoggingOutput (2 tests) PASSED
tests/test_logging.py::TestLoggingLevels (2 tests) PASSED
tests/test_logging.py::TestLoggingHandlers (2 tests) PASSED
tests/test_task_manager.py::test_create_and_get_task PASSED
tests/test_tools.py::test_tools_create_and_list PASSED
======================== 15 passed ========================
The reminder system uses system cron + scripts (never OpenClaw native cron).
User: "remind me at 6pm to take socks"
β
βΌ
Agent parses: WHAT="take socks", WHEN="18:00 IST", TYPE=one-shot
β
βΌ
Agent adds line to .crontab:
0 18 24 3 * REMINDER_MSG="take socks" /usr/bin/python3 scripts/reminder-runner.py # REMINDER_x1y2z3
β
βΌ
Agent runs: crontab .openclaw/workspace/.crontab
β
βΌ
At 18:00 IST, cron fires reminder-runner.py
β
βΌ
reminder-runner.py sends "π Reminder: take socks" via Telegram
β
βΌ
reminder-runner.py removes its own line from .crontab and re-syncs
OpenClaw has a built-in cron system (openclaw cron add), but it was found to be unreliable:
- CLI (
openclaw cron list) returns exit code 1 in version 2026.3.13 - Agent hallucinates about the cron flags and syntax
- No reliable way to verify if a cron job was actually created
- MEMORY.md explicitly states: "Never use openclaw native cron for reminders"
The agent has exactly one reliable path for setting reminders:
# Step 1: Add to workspace .crontab
MM HH DD MM * REMINDER_MSG="<message>" /usr/bin/python3 /home/sanchitingale339/.openclaw/workspace/scripts/reminder-runner.py # REMINDER_<tag>
# Step 2: Sync to system
crontab /home/sanchitingale339/.openclaw/workspace/.crontabThe reminder-runner.py script:
- Reads message from
REMINDER_MSGenv var - Sends via
openclaw message sendto Telegram (with 3 retries) - Removes its own line from
.crontab - Re-syncs system crontab
OpenClaw communicates with TaskManager via JSON-RPC 2.0 over stdin/stdout:
python3 main.py stdioAvailable tools (via tools/list):
create_taskβ Create a new taskcreate_urgent_taskβ Create high-priority taskadd_to_projectβ Add task to projectcreate_recurring_taskβ Create recurring taskbatch_create_tasksβ Create multiple taskscomplete_taskβ Mark task completedelete_taskβ Delete a tasklist_tasksβ List tasks with filtersset_reminderβ Set system cron reminderget_scheduleβ Get daily scheduleget_schedule_suggestionsβ AI schedule suggestionsupdate_routineβ Update work routineadd_holidayβ Add holidayadd_comp_offβ Add comp-off daylist_projectsβ List all projectsget_task_detailsβ Get task details
python3 main.py serve 5000Endpoints documented in README.md. All return JSON with {ok: bool, data/message} structure.
The openclaw_skill.yaml file registers TaskManager as an OpenClaw skill. It defines:
- Tool schemas (parameters, types, required fields)
- API endpoints
- Health check configuration
The system runs in IST (Asia/Kolkata, UTC+5:30), but OpenClaw's agent sometimes shows UTC times in its reasoning. When the agent converted "9:30 PM IST" to "16:00 UTC" and tried to use the UTC value for cron scheduling, reminders would fire at the wrong time.
Single source of truth: All times are IST.
- System timezone:
timedatectlβAsia/Kolkata - Environment:
TZ=Asia/Kolkatain/etc/environment - Cron daemon: Uses system timezone (IST)
- All scripts: Use
datetime.now()(system-local, IST) .crontabentries: IST times directly
Rule in MEMORY.md:
"When the user says '9:30 PM', use
30 21 * * *directly β do NOT subtract 5:30"
- Task CRUD (create, read, update, delete, complete)
- Project management
- Priority levels and labels
- AI-powered scheduling based on work routine
- Holiday and comp-off awareness
- System cron reminders via Telegram
- Flask REST API
- JSON-RPC stdio mode for OpenClaw
- 15/15 tests passing
- No authentication on API (localhost only)
- Development Flask server (not production-grade)
- Naive datetimes (no explicit timezone objects in code)
- Some overdue tasks in
my_tasks.jsonare system-managed (LeetCode, Morning Brew) and should not be manually completed
These are tracked but likely auto-managed:
- "Solve LeetCode problem" (due 2026-03-11) β has daily cron reminders
- "Check Google Cloud Console billing" (due 2026-03-12) β has daily cron reminder
- "Morning Brew 10-day learning program" (due 2026-03-23) β has daily cron generator
- "Buy a 100-page spiral book" (due 2026-03-23) β personal, needs manual completion
- "Take the notebook" (due 2026-03-23) β personal, needs manual completion
The agent's hallucination loop was caused by conflicting instructions in MEMORY.md. When the same file says both "use openclaw cron" and "never use openclaw cron," the agent goes in circles. One rule, one path, no ambiguity.
OpenClaw's native cron proved unreliable. System cron is:
- Independent of the application process
- Survives restarts
- Verifiable with
crontab -l - Standard Unix β no vendor lock-in
One-time reminders that don't clean up after themselves leave stale crontab entries. The reminder-runner.py pattern of removing its own entry after execution keeps the crontab clean.
Two tests were silently never running because of an indentation error. Regular test audits (pytest --collect-only) catch this.
When TaskManager defaults to one file and main.py defaults to another, bugs hide in plain sight. Always align defaults across entry points.
Relying on implicit timezone behavior leads to "works on my machine" bugs. Setting TZ=Asia/Kolkata explicitly in the environment and documenting "IST only, never convert" prevents entire classes of bugs.