# todos — a shared todo / task / reminder store for your AI agents ## Intent You talk to several AI agents (a coding agent, a chat assistant, an email agent…). Each one hears "remind me to…" or "add a task for…" but keeps nothing between conversations, and none of them sees what the others noted. This package gives all of them **one durable, shared list** inside Kody. Any agent connected to your Kody account can create, list, update, complete and delete items, produce a daily digest, and remind you when things are due. Every change records which agent made it. Success: you can throw a todo at any agent, ask any other agent "what's on my plate?", and get the same answer. No UI. It is a set of package exports agents call from Kody `execute`. ## Quick start 1. **Fork / install** the package from the Kody community page into your own account. Your fork has its own private storage; nothing is shared with the original author. The examples below use the original scope `@charlesmatte/todos`; replace it with **your** scope after forking (for example `kody:@yourname/todos/create`). 2. **Set your time zone once** (default is `UTC`): ```js import settings from 'kody:@charlesmatte/todos/settings' export default async () => settings({ timeZone: 'America/New_York' }) ``` 3. **First calls**: ```js import createTodo from 'kody:@charlesmatte/todos/create' import listTodos from 'kody:@charlesmatte/todos/list' import digest from 'kody:@charlesmatte/todos/digest' export default async () => { const { todo } = await createTodo({ title: 'Renew domain name', due: '2026-11-01', priority: 'high', tags: ['admin'], by: 'my-assistant' }) const { text } = await listTodos({}) // open items, sorted const daily = await digest({}) // overdue / today / next 7 days / undated return { id: todo.id, text, digest: daily.text } } ``` ## Export reference All exports take one plain object. **Unknown keys are rejected** with an error listing the accepted shape. `null` values count as "not provided". Every write returns the stored record and a one-line `line` summary, e.g. `T12 [high] Renew domain name — due 2026-11-01 (project: Website) #admin`. | Export | Input | Returns | | --- | --- | --- | | `.` (overview) | — | export list, current time zone, rules, docs pointer | | `./settings` | `{ timeZone? }` | `{ timeZone, configured, default, changed }` | | `./create` | `{ title, by, notes?, due?, priority?, tags?, kind?, project?, client?, timeZone? }` | `{ todo, line, timeZone }` | | `./list` | `{ status?, overdue?, today?, dueBefore?, dueAfter?, undated?, tag?, project?, client?, kind?, priority?, q?, includeDeleted?, limit?, timeZone? }` | `{ today, timeZone, total, count, todos, text }` | | `./get` | `{ id, timeZone? }` | `{ todo, overdue, line }` (todo includes history) | | `./update` | `{ id, by, title?, notes?, due?, clearDue?, snooze?, priority?, kind?, tags?, addTags?, removeTags?, project?, client?, timeZone? }` | `{ todo, line, changes }` | | `./complete` | `{ id, by, note? }` | `{ todo, line }` | | `./reopen` | `{ id, by }` | `{ todo, line }` | | `./delete` | `{ id, by, purge? }` | `{ id, deleted, purged }` | | `./digest` | `{ days?, includeUndated?, tag?, project?, client?, timeZone? }` | `{ today, timeZone, counts, sections, text }` | Field details: - `title` (≤300 chars), `notes` (≤5000), `project` / `client` (≤120): free text. - `priority`: `"low" | "normal" | "high"` (default `normal`). `kind`: `"todo" | "task"` (default `todo`), a label only. - `tags`: array of strings. Stored lowercase, a leading `#` is stripped, duplicates removed. - `id`: `"T12"` or `"12"`. - `list.status`: `"open"` (default) | `"done"` | `"all"`. `dueBefore`/`dueAfter` are inclusive; date-only bounds cover the whole day. `q` matches every word against title, notes, tags, project, client, id. `limit` 1–500 (default 100). - `update.snooze`: exactly one of `{ days: 1-365 }` (shifts from the current due date, or from today if overdue/undated, keeping the time of day), `{ hours: 1-720 }`, `{ until: "2026-10-20" } (any due format)`. Use only one of `due`, `clearDue`, `snooze`. To clear text fields pass `""` (`notes`, `project`, `client`). `tags` replaces; `addTags`/`removeTags` edit. - `digest.days`: 1–60 (default 7). ### Examples ```js import updateTodo from 'kody:@charlesmatte/todos/update' import completeTodo from 'kody:@charlesmatte/todos/complete' import reopenTodo from 'kody:@charlesmatte/todos/reopen' import deleteTodo from 'kody:@charlesmatte/todos/delete' import getTodo from 'kody:@charlesmatte/todos/get' import listTodos from 'kody:@charlesmatte/todos/list' await listTodos({ overdue: true }) await listTodos({ dueBefore: '2026-10-20', tag: 'admin', priority: 'high' }) await listTodos({ q: 'invoice', status: 'all' }) await updateTodo({ id: 'T12', by: 'my-assistant', snooze: { days: 2 } }) await updateTodo({ id: 'T12', by: 'my-assistant', due: '2026-10-20T14:00', addTags: ['urgent'] }) await completeTodo({ id: 'T12', by: 'my-assistant', note: 'paid online' }) await reopenTodo({ id: 'T12', by: 'my-assistant' }) await deleteTodo({ id: 'T12', by: 'my-assistant' }) // soft delete const { todo } = await getTodo({ id: 'T12' }) // includes todo.history ``` ## Due date formats | Input | Meaning | | --- | --- | | `"2026-10-20"` | all-day item, due by the end of that day in the store time zone | | `"2026-10-20T14:00"` | 14:00 wall-clock time in the store time zone | | `"2026-10-20T18:00:00Z"`, `"2026-10-20T14:00-04:00"` | exact instant (shown in the store time zone) | | `"today"`, `"tomorrow"` | all-day, relative to the store time zone | Natural language such as "next Friday" is rejected. The calling agent should convert it first. Each stored due keeps `{ date, time, at, timeZone }`: `at` is the UTC instant for timed items, `null` for all-day ones. ## Time zone setup - The store time zone is a per-store setting (`./settings`), `UTC` until you set it. Set it once to your IANA zone (`America/Toronto`, `Europe/Paris`, …). Invalid names are rejected. - It governs: parsing date-only and wall-clock dues, `today`/`tomorrow`, "overdue" for all-day items, `list.today`, and digest buckets. - Any time-aware export also accepts a per-call `timeZone` override. - Changing the setting later does not rewrite existing items: timed items keep their exact instant, all-day items keep their calendar date. ## The `by` convention Every write requires `by`: a short, stable name for the agent (or person) making the change, e.g. `"claude-code"`, `"email-agent"`, `"me"`. It is saved as `createdBy` / `updatedBy` and in each history entry (`{ at, by, action, changes }`, last 50 kept), so you can see who added or changed what. ## Delete vs purge `./delete` is a **soft delete**. The item is archived, hidden from `list` and `digest`, visible with `list({ includeDeleted: true })`, and restorable with `./reopen`. `purge: true` removes it permanently and cannot be undone. Agents should purge only when you explicitly ask. ## Concurrency Storage is this package's durable SQLite (`packageStorage()`), shared by every caller of your copy. Ids come from an atomic counter (`T1`, `T2`, …; ids are never reused). Updates use optimistic versioning: an edit applies only if the item did not change since it was read, otherwise it re-reads and retries up to 5 times. Two agents editing the same item at once therefore never silently overwrite each other. ## Recipes ### Daily digest job Create a Kody job (search Kody for "schedule a job") that runs each morning with this module, and send `text` wherever you like (email, chat): ```js import digest from 'kody:@charlesmatte/todos/digest' export default async () => { const { text, counts } = await digest({ days: 7 }) if (counts.overdue + counts.today + counts.upcoming === 0) return { skipped: true } return { text } // hand this to your email / chat integration } ``` ### Due reminders Run every 15–30 minutes and notify about items that just became due: ```js import listTodos from 'kody:@charlesmatte/todos/list' export default async () => { const soon = new Date(Date.now() + 30 * 60_000).toISOString() const { todos } = await listTodos({ dueBefore: soon, dueAfter: new Date().toISOString() }) return todos.filter((t) => t.due?.at).map((t) => t.line) // timed items due in the next 30 min } ``` ### Agent prompt snippet Paste into your agents' instructions or memory: > Store all my todos, tasks and reminders in the Kody package `todos` (`kody:@YOUR_SCOPE/todos/...`). > When I say "remind me", "add a todo/task", or "note that I need to…", call `./create` with a clear > title, a due date in `YYYY-MM-DD` or `YYYY-MM-DDTHH:MM` (my time zone) if I gave one, and > `by: "YOUR_AGENT_NAME"`. When I ask what's on my plate, call `./digest` or `./list` and show the `text`. > Use `./complete`, `./update` (including `snooze`) and `./delete` with the item id (`T12`). Never use > `purge` unless I ask. ## Limitations - Single list per installed copy: no multi-user sharing, assignees, or per-agent permissions. Any agent with access to your Kody account can change any item. - No recurring items, subtasks or attachments. History keeps the last 50 entries per item. - No built-in notifications: pair it with a Kody job (see recipes) to send digests and reminders. - Filtering happens in memory, which is fine for thousands of items but not designed for very large stores. - Due dates must be explicit; natural-language dates are not parsed.