For AI agents
The file is the API.
Everything Dumi knows is one JSON file on the Mac. Read it, edit it, write it back — Dumi notices within a second, and a task your agent finishes grows the bonsai exactly as if you had ticked it. No API keys, no server, no SDK. The same text, for pasting into a system prompt: llms.txt.
Where
~/Library/Application Support/Dumi/data.json
If the environment variable ISLAND_DATA is set when Dumi starts, that path is used instead.
Shape
A JSON object. Two keys are yours to edit; leave the others (sessions, timer, prefs, bonsai) exactly as they are.
{
"lists": [
{ "id": "6D2B3A1C-…", "name": "My Tasks" }
],
"tasks": [
{
"id": "F0A1…", // UUID, upper-case
"listID": "6D2B3A1C-…", // id of a list above
"parentID": "…", // optional: makes this a subtask
"title": "Reply to Sarah",
"notes": "Re: Friday offsite", // optional
"due": "2026-09-18T00:00:00Z", // optional, ISO 8601
"starred": false,
"createdAt": "2026-09-14T08:12:00Z",
"completedAt": "2026-09-14T09:30:00Z", // present = done; absent = open
"order": -3, // lower sits higher in its list
"rewarded": true // Dumi's own bookkeeping; don't set it
}
],
"sessions": [ … ], "timer": { … }, "prefs": { … }, "bonsai": { … }
}
Read
Open tasks are those without completedAt; top-level ones have no parentID. Sort by order ascending within a list.
jq '[.tasks[] | select(.completedAt == null and .parentID == null)
| {id, title, due, starred}]' \
~/Library/Application\ Support/Dumi/data.json
Add
Append an object to tasks. Only title is required: a missing id gets one, a missing or unknown listID lands in the first list, a missing order puts the task at the top, like a task typed into the island.
{ "title": "Book flights to Tokyo", "listID": "6D2B3A1C-…", "due": "2026-09-18T00:00:00Z" }
Complete, reopen, edit
- Complete: set completedAt to now, ISO 8601 in UTC (2026-09-14T09:30:00Z). Completing a task completes its open subtasks. Dumi grants the cubes and sets rewarded itself.
- Reopen: remove completedAt.
- Edit: change title, notes, due, starred, order, listID freely. Delete a task by removing its object (and its subtasks).
- Lists: add { "id", "name" } to lists; a removed list takes its tasks with it.
Write
Read the whole file, change it, write the whole file back atomically — to a temporary file in the same folder, then rename over data.json. Dumi writes the same way. Keep the other top-level keys untouched. If Dumi is running it picks the change up within about a second and shows the growth; if not, it reads the file on launch.
import json, os, uuid, datetime, tempfile
P = os.path.expanduser("~/Library/Application Support/Dumi/data.json")
d = json.load(open(P))
now = datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
# complete every open task whose title starts with "Reply"
for t in d["tasks"]:
if "completedAt" not in t and t["title"].startswith("Reply"):
t["completedAt"] = now
# add one
d["tasks"].append({"id": str(uuid.uuid4()).upper(), "listID": d["lists"][0]["id"],
"title": "Send the deck", "createdAt": now})
fd, tmp = tempfile.mkstemp(dir=os.path.dirname(P), suffix=".json")
with os.fdopen(fd, "w") as f: json.dump(d, f, indent=2)
os.replace(tmp, P)
Rules
- Cubes are earned, never written. Anything you put in bonsai is ignored and overwritten.
- A completion earns cubes once. Reopening and completing the same task again earns nothing.
- Half-written or invalid JSON is ignored until the next complete write; Dumi never loses its own state to a bad file. An unreadable file at launch is set aside as data.json.corrupt-….
- Dumi saves about half a second after the person's last change. An edit that lands in that window can be overwritten — read again if a write seems not to have taken.