Skip to content

README.md

185 lines · 9.4 KB · Markdown

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):

    import settings from 'kody:@charlesmatte/todos/settings'
    export default async () => settings({ timeZone: 'America/New_York' })
  3. First calls:

    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.

ExportInputReturns
. (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
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

InputMeaning
"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):

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:

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.