# Tracked Todo Working Memory

> Complete guide for GAIA tracked todos — philosophy, two modes (immediate/long-running), the canvas.md / activity.md files, scheduling/recurrence, and institutional memory.

- Skill: `theexperiencecompany/tracked-todo-working-memory` (Agent Skill)
- Install (CLI): `npx skillmds@latest add theexperiencecompany/tracked-todo-working-memory`
- Raw SKILL.md: https://api.skillmd.com/api/skills/theexperiencecompany/tracked-todo-working-memory/raw
- Safety review: pending (external: skill-scanner PASS, skillspector PASS)
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Integrations & APIs
- Author: theexperiencecompany (https://skillmd.com/u/theexperiencecompany)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/theexperiencecompany/tracked-todo-working-memory

---


# 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 with `edit` (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 with `edit` (or `read` then `write`). 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 folder
- `update_tracked_todo` — update labels, due_date, priority, scheduled_at, recurrence, expires_at, references
- `complete_tracked_todo` — mark done, requires completion summary
- `search_todo_context` — semantic search across all notes (canvas + activity, ChromaDB); includes completed
- `list_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):

```markdown
# {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:

```python
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:

```markdown
- 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 title
- `description` — what needs to happen and expected outcome
- `initial_canvas` — markdown content; default template if omitted
- `labels` — list of strings; `gaia-tracked` added automatically
- `priority` — `high` | `medium` | `low` | `none` (default `none`)
- `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 (no `scheduled_at` needed); shortcut values still need `scheduled_at` as 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, needs `scheduled_at` as anchor)
- `weekly` — +7 days (shortcut, needs `scheduled_at`)
- `every_4h` — +4 hours (shortcut, needs `scheduled_at`)
- `every_1h` — +1 hour (shortcut, needs `scheduled_at`)
- Cron — `0 9 * * 1-5` = weekdays 9am user-local; `0 9,20 * * *` = 9am and 8pm daily. ONE recurrence, not two todos. No `scheduled_at` needed — first fire is computed from the cron.

### `due_date` vs `expires_at`

- **`due_date`** = deadline. Overdue tasks still need doing. Set via `update_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_at` on open-ended tasks with no natural expiry.

### Validation

- `scheduled_at` must be future
- Shortcut `recurrence` (`daily`, `weekly`, `every_4h`, `every_1h`) requires `scheduled_at` as anchor. Cron does not.
- Cannot clear `scheduled_at` while a shortcut `recurrence` is set
- Cron expressions validated via croniter
- If both `scheduled_at` and a cron `recurrence` are passed, `scheduled_at` is 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: `failed` label added, user notified
- Success with recurrence: `scheduled_at` advances, 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_notification` to announce it — that sends it twice.
- Nothing worth saying? End with an empty message and nothing is sent.
- `notify_on_run` (default `True`, 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
  deliberate `send_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

1. Check the `ACTIVE TRACKED TODOS:` block in your context — does the request relate to an existing todo? Each line names its folder.
2. If yes: `read` its canvas.md, then act, then update canvas.md / activity.md
3. 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

1. Write `## Learnings` in canvas.md
2. `complete_tracked_todo(todo_id="...", summary="...")` — marks completed in DB + ChromaDB

## Examples

### Immediate: send an email

```python
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

```python
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

```python
create_tracked_todo(
    title="Daily HN top posts summary", scheduled_at="2026-03-26T08:00:00Z", recurrence="daily"
)
```

### Recurring: weekday cron

```python
create_tracked_todo(
    title="Weekday standup prep", scheduled_at="2026-03-26T09:00:00Z", recurrence="0 9 * * 1-5"
)
```

### Update after creation

```python
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

