Tracked Todo Working Memory
Philosophy
Tracked todos are GAIA-managed todos: they show on the user's todos page like a normal todo, but GAIA owns them and keeps working notes as files (and an optional schedule) so it can act on them over time. They are distinct from the user's own hand-created action items. They record what GAIA did, when, how, and why, so future conversations can find and build on past work.
When the user says "email Rahul about the contract" and months later asks "what happened with Rahul's contract?", the tracked todo and its files surface the answer.
One todo per initiative. "Email Rahul, create a Linear issue, follow up Friday" = ONE tracked todo ("Contract negotiation with Rahul") whose canvas.md holds the email thread ID, Linear issue URL, and follow-up schedule.
Files
Every tracked todo is a folder under /workspace/gaia-tasks/<slug>-<shortid>/ (the create result and the ACTIVE TRACKED TODOS block both name it):
canvas.md— the recall doc. What you want to know later: Key Details, Current State, Context, Learnings. Edit it withedit(rewrite the section that changed); never append entries to the end.activity.md— the dated log, oldest first, newest at the end. One entry per thing that happened. Add entries withedit(orreadthenwrite). Scheduled runs stamp their own start/finish markers here.log.md,meta.json,index.md— generated by the system; read-only.
Read and write these with the ordinary read / edit / write tools. They are stored on the todo itself, so this works even when the folder is not on disk (bash sees a read-only copy; cat and grep -r are fine there, sed -i is not).
Tools
Always available to the executor — no retrieve_tools needed:
create_tracked_todo— create a todo; the result names its folderupdate_tracked_todo— update labels, due_date, priority, scheduled_at, recurrence, expires_at, referencescomplete_tracked_todo— mark done, requires completion summarysearch_todo_context— semantic search across all notes (canvas + activity, ChromaDB); includes completedlist_tracked_todos— list all active tracked todos (up to 50) with full metadata
Search First, Create Last
Creating a new todo is the last step, not the first. Always search before creating.
search_todo_context(query="relevant keywords")
- Active match → update its files; do NOT create. "Related action" = same initiative, person, system, or goal. Always update, even for follow-on steps.
- Completed match, same initiative resuming → create new ONLY if the user explicitly asked GAIA to DO something for this initiative again. Never create just because search returned a historical match during an unrelated request.
- No match → create — only if GAIA performed or scheduled a real write/action this turn.
Create when GAIA performs or schedules an action on an external system (email, calendar, Slack, Linear, Notion, etc.) that it needs to remember, follow up on, or repeat — and nothing relevant already exists in memory.
Do NOT create for:
- Pure reads with no side effects ("what's the weather?", "summarize my emails") — no matter how complex or how often they run; a recurring daily summary is still a read, and saving the summary as a todo is not tracking
- Steps in your current orchestration (use
plan_tasks) - Casual conversation or one-off questions
- Anything clearly continuing an existing tracked todo — update that one instead
Overusing tracked todos degrades search quality and clutters GAIA's memory.
Two Modes
Once you've confirmed no existing todo covers this (see Search First above):
Immediate
Completes in this conversation. Create → delegate → document → complete.
search_todo_context → (nothing relevant found) → create_tracked_todo
→ handoff to subagent → collect activity report
→ edit activity.md (dated entry at the end) and canvas.md (Current State, Learnings)
→ complete_tracked_todo
Long-Running
Spans conversations or needs follow-up. Create → act → update → leave open.
search_todo_context → (nothing relevant found) → create_tracked_todo(scheduled_at=..., ...)
→ act → edit activity.md + canvas.md → leave open
→ (future conversation) find via active todos or search → read canvas.md → act → update
→ eventually: complete_tracked_todo with learnings
- "Send Rahul the report" — search first; if nothing found: immediate todo.
- "Email Rahul about the meeting" — search first; if nothing found: long-running todo.
- "He replied, send thanks" — search finds existing todo → update its files, no new todo.
- "What's the weather?" / "Summarize my emails" — no todo.
canvas.md
Default template (used when initial_canvas is omitted):
# {title}
## Key Details
<!-- email addresses, thread IDs, calendar IDs, issue URLs: everything needed to act -->
## Current State
<!-- what is true RIGHT NOW; rewrite after every action -->
## Context
<!-- accumulated context from signals, related information, decisions made, open questions -->
## Learnings
<!-- written ONLY at completion time: what worked, what did not, timing insights, reusable patterns -->
Keep it short and current. To change a section, edit that section's body:
edit(
path="/workspace/gaia-tasks/rahul-contract-follow-up-5f10e407/canvas.md",
old_string="## Current State\nInitial email sent. Waiting for reply.",
new_string="## Current State\nRahul replied 2026-03-27; drafting the counter-proposal.",
)
activity.md
Chronological, oldest first, one dated entry per event. After subagents return, record their structured reports here:
- 2026-03-26T09:12:00+00:00 Gmail agent: sent email to rahul@example.com re: Q2 contract renewal.
Tools: GMAIL_CREATE_DRAFT → GMAIL_SEND_DRAFT. Thread ID: 18f3a2b. Draft approved and sent.
- 2026-03-26T09:14:00+00:00 Linear agent: created issue LIN-423 "Track Q2 contract renewal".
Tools: LINEAR_CREATE_ISSUE. URL: https://linear.app/team/LIN-423.
- 2026-03-27T09:00:00+00:00 ▶ scheduled run started (conversation_id=3f9a1c2e)
Add an entry by editing the end of the file (read it first when unsure of the last line), or read then write the whole file. Never put activity in canvas.md, and never put learnings here.
System Log
log.md is auto-written by the system (creation, file writes, completion). Don't write to it directly.
Create Fields
title(required) — short descriptive titledescription— what needs to happen and expected outcomeinitial_canvas— markdown content; default template if omittedlabels— list of strings;gaia-trackedadded automaticallypriority—high|medium|low|none(defaultnone)scheduled_at— ISO datetime when GAIA should auto-execute (must be future). Omit for cron recurrence — first fire is computed from the cron.recurrence— repeat pattern. Cron-style works alone (noscheduled_atneeded); shortcut values still needscheduled_atas anchor.expires_at— ISO datetime when todo becomes irrelevant (skipped if expired)
due_date is only settable via update_tracked_todo, not at creation time.
Scheduling & Recurrence
scheduled_at
ISO datetime, must be in the future. GAIA auto-executes via background worker at that time.
recurrence
ALWAYS evaluated in the user's stored timezone — pass cron in user-local wall-clock terms, the backend converts to UTC. Do NOT bake offsets into the cron string. After successful execution, scheduled_at auto-advances and a new job is enqueued.
daily— +1 day (shortcut, needsscheduled_atas anchor)weekly— +7 days (shortcut, needsscheduled_at)every_4h— +4 hours (shortcut, needsscheduled_at)every_1h— +1 hour (shortcut, needsscheduled_at)- Cron —
0 9 * * 1-5= weekdays 9am user-local;0 9,20 * * *= 9am and 8pm daily. ONE recurrence, not two todos. Noscheduled_atneeded — first fire is computed from the cron.
due_date vs expires_at
due_date= deadline. Overdue tasks still need doing. Set viaupdate_tracked_todo.expires_at= relevance window. Expired tasks are skipped entirely.- Both can be set together (e.g., "file taxes": due April 15, expires April 15).
- Don't set
expires_aton open-ended tasks with no natural expiry.
Validation
scheduled_atmust be future- Shortcut
recurrence(daily,weekly,every_4h,every_1h) requiresscheduled_atas anchor. Cron does not. - Cannot clear
scheduled_atwhile a shortcutrecurrenceis set - Cron expressions validated via croniter
- If both
scheduled_atand a cronrecurrenceare passed,scheduled_atis ignored (first fire comes from the cron)
Execution & Retry
- Background worker (ARQ) runs at
scheduled_at - Redis lock prevents concurrent execution of same todo
- Failure: retries up to 3× with backoff (1 hour, then 4 hours)
- After 3 failures:
failedlabel added, user notified - Success with recurrence:
scheduled_atadvances, new job enqueued
Where a run's result goes
- The run's final message is delivered to the user's chat app automatically when it finishes (WhatsApp/Telegram/Discord/Slack), as a normal GAIA message.
- So the answer IS the user-facing message: write it for them, and do NOT also
call
send_notificationto announce it — that sends it twice. - Nothing worth saying? End with an empty message and nothing is sent.
notify_on_run(defaultTrue, settable on create/update) turns delivery off for a todo whose runs the user should not hear about, e.g. a frequent poll that usually finds nothing. A silent todo reaches the user only via a deliberatesend_notification.
Institutional Memory
References
Manually link related past todos:
update_tracked_todo(todo_id="abc", references=["old_todo_id_1"])
References are appended (not replaced). Use search_todo_context to find past todos worth referencing, then read their canvas.md to understand past approaches.
Writing Learnings Before Completion
Before calling complete_tracked_todo, edit the ## Learnings section of canvas.md. Future similar tasks will reference these.
Good: "Sarah responds in 2-3 days", "approval takes 1 week", "batch the Linear + Notion updates in one handoff" Bad: "went well", restating the timeline, obvious observations
Lifecycle
Before Acting
- Check the
ACTIVE TRACKED TODOS:block in your context — does the request relate to an existing todo? Each line names its folder. - If yes:
readits canvas.md, then act, then update canvas.md / activity.md - If unclear:
search_todo_context(query="...")to check for duplicates
After Acting
- Add dated entries to activity.md from subagent reports
- Rewrite Current State in canvas.md
- Update properties if needed (
update_tracked_todo)
Completing
- Write
## Learningsin canvas.md complete_tracked_todo(todo_id="...", summary="...")— marks completed in DB + ChromaDB
Examples
Immediate: send an email
create_tracked_todo(
title="Sent Q2 report to Sarah",
initial_canvas="# Sent Q2 report to Sarah\n\n## Key Details\n- Recipient: sarah@example.com\n\n## Current State\n\n## Learnings\n",
)
# handoff to Gmail → collect report → add entry to activity.md → complete
Long-running: follow-up with expiry
create_tracked_todo(
title="Follow up with Rahul re: contract",
description="Sent initial email. Follow up if no reply.",
scheduled_at="2026-04-01T09:00:00Z",
expires_at="2026-04-08T00:00:00Z",
initial_canvas="# Rahul Contract Follow-up\n\n## Key Details\n- Email: rahul@example.com\n- Thread ID: 18f3a2b\n- Contract: Q2 vendor agreement\n\n## Current State\nInitial email sent. Waiting for reply.\n\n## Learnings\n",
)
# then record the send at the end of activity.md:
# - 2026-03-25T14:02:00+00:00 Gmail agent: sent email re: Q2 contract. Tools: GMAIL_CREATE_DRAFT → GMAIL_SEND_DRAFT. Thread ID: 18f3a2b.
Recurring: daily check
create_tracked_todo(
title="Daily HN top posts summary", scheduled_at="2026-03-26T08:00:00Z", recurrence="daily"
)
Recurring: weekday cron
create_tracked_todo(
title="Weekday standup prep", scheduled_at="2026-03-26T09:00:00Z", recurrence="0 9 * * 1-5"
)
Update after creation
update_tracked_todo(todo_id="abc123", due_date="2026-04-15")
update_tracked_todo(todo_id="abc123", scheduled_at="2026-03-30T10:00:00Z")
update_tracked_todo(todo_id="abc123", scheduled_at="", recurrence="") # Clear scheduling
update_tracked_todo(todo_id="abc123", labels=["gaia-tracked", "waiting-for-reply"])
Anti-Patterns
- Not creating a tracked todo when GAIA touched external systems (even "just" sending an email)
- Multiple todos for one initiative (one email todo + one Linear todo + one Notion todo → should be one)
- Vague notes ("made progress") instead of specific details with IDs and tool names
- Not collecting activity reports from subagents before writing activity.md
- Appending activity to canvas.md (it belongs in activity.md; canvas.md is for what is true now)
- Not searching before creating — duplicates make future lookups confusing
- Not writing learnings before completing — wastes institutional memory