OpenTodo 0.3.0
Released on
OpenTodo 0.3.0 is about bringing your own AI, if you want one. There is a chat assistant inside the app that answers questions about your tasks and proposes changes you review and undo in one step, with Anthropic, OpenAI, Gemini or a model on your own network. An assistant you already use can now make changes over MCP, and the ot command is ready to be driven by coding agents. All of it is off until the owner turns it on; nothing is sent anywhere before that. Use AI with OpenTodo compares the three options. Settings is also split into sections with their own pages, and a project can be shared with a single invite link.
Upgrade notes
- Migrations
0026to0029(agent tokens and switches; assistant consent and usage; members' own assistant provider; project invite links) are applied automatically at startup. Back up/databefore upgrading: there are no down-migrations. No new environment variables are required;OPENTODO_MCP_WRITESis optional. - Image tags. The release publishes
ghcr.io/pixari/opentodo:0.3.0and:0.3forlinux/amd64andlinux/arm64(:latestfollowsmain). The Coolify template now follows:0.3; if you use:0.2, change the tag in Edit Compose file and click Deploy. otscripts: destructive commands now need--yesoff a terminal, and refusals exit 5 instead of 1 (see Breaking below).- Binary size. The server binary grows from about 17 MB to about 23 MB, mostly the official Anthropic SDK used by the assistant.
Breaking
ot: destructive commands need--yeswithout a terminal.ot rm,ot project rm,ot token revokeandot auth logoutused to proceed silently when stdout was not a terminal or--jsonwas given. They now refuse with exit code 2 (confirmation_required) before sending anything. Migration: add--yesto scripts and cron jobs that delete on purpose, e.g.ot rm "$ID" --yes. On a terminal without--jsonthey still ask. (CLI)ot: new exit code 5, "not permitted". Refusals for scope, role, a switch, encryption or read-only mode (insufficient_scope,forbidden,not_permitted,writes_disabled_by_owner,writes_disabled_by_user,encryption_enabled,read_only) exit 5 instead of 1, so scripts and agents can stop instead of retrying. Migration: scripts that test for exactly1should also accept5; scripts that test for non-zero are unaffected. The op log and the REST API are unchanged.
Added
- Invite to a project with a link. In a project's Share dialog, the owner and admins can create a single-use invite link with a role (viewer or editor; admin for the owner), an optional email binding and an expiry from 1 hour to 30 days (default 7 days). The link is shown once; pending links are listed with their role, email, expiry and creator and can be revoked. Opening
/invite/p/<token>shows who invited you to which project: signed in, you select Join project; otherwise you sign in and join, or create an account that joins in the same step. A link creates accounts only when its creator is the server owner or an admin, when open sign-up is on, or when the owner switches on Members can invite new people with project links in Settings → Administration (off by default); otherwise the page asks the person to get an account first. Links stop working when revoked, used or expired, when the project is deleted, or when their creator is no longer the project's owner or an admin. Links are server-side data, never synced; refused on the demo instance. (Sharing, API) - New routes:
GET/POST /api/v1/projects/{id}/invites,DELETE /api/v1/projects/{id}/invites/{inviteId},GET /api/v1/project-invites/{token}(public preview),POST /api/v1/project-invites/{token}/accept,GET/PUT /api/v1/settings/project-invites;POST /api/v1/auth/registeracceptsprojectInvite. New error codeproject_invite_not_allowed; audit eventsproject_invite.created,project_invite.revoked,project_invite.accepted. Migration0029_project_invites.sqlis applied automatically. - Your own assistant provider. When the owner lets members bring their own key, each person can choose Anthropic, OpenAI or Google Gemini, a model and their own key in Settings → AI → Assistant → Which assistant you use, with Test connection. Members are never offered the compatible endpoint, so they cannot make the server connect to an address of their choosing. The disclosure names the provider actually used ("from this server to … with your own API key") and is asked again when it changes. Turning member keys off makes everyone use the owner's provider again; choices are kept for later. Keys stay in
<data>/secrets/assistant.json, never returned, logged, exported or backed up. (AI assistant) - Own keys outside the allowance. Answers on a member's own key are recorded and shown as "your key", but neither counted nor stopped by the monthly token allowance unless the owner turns on the new Count members' own keys toward the monthly limit (
cap_member_keys). One answer per person and 8 per server still apply. The owner sees how many people use their own provider and key. - API (additive):
own_provider,own_modeland new status fields onGET/PUT /api/v1/me/assistant;POST /api/v1/me/assistant/test(signed-in sessions, 5 per 10 minutes);cap_member_keysandown_key_memberson/api/v1/settings/assistant;counted_tokensandown_keyon the usage route andown_keyon the turn'susageevent. (API) - AI assistant in the app. A chat panel (Shift+I or Assistant in the header; a side sheet on wide screens, full screen on phones) that answers questions about your tasks and proposes changes: "move everything overdue to Friday", "break this task into steps". Each proposal is a reviewable list with every change in words; you apply the checked part (at most 50 changes) and undo it in one step. Applied changes are ordinary edits from your device: offline-capable, synced, labelled Assistant in history. Providers, each with the owner's or a member's own key: Anthropic through the official Anthropic Go SDK (default
claude-opus-5-5;claude-sonnet-5-5andclaude-haiku-5-5offered), OpenAI, Google Gemini and any OpenAI-compatible endpoint, including Ollama or LM Studio on your network. Off by default: the owner enables it in Settings → Assistant (provider, models, keys, private endpoints, monthly token allowance, member keys, Test connection); each person accepts a disclosure naming the provider and the fields sent before their first message, and again when the provider changes. Keys live only in<data>/secrets/assistant.json, never returned, logged, exported or backed up. The assistant cannot change sharing, members, settings or tokens, make outbound requests or delete permanently; task text is marked as untrusted. Conversations stay on the device and are deleted at sign-out. Not available for end-to-end encrypted accounts or on the demo instance. Optional per-device auto-apply of up to 3 small, reversible changes, off by default. (AI assistant, API, security) - New routes:
GET/PUT /api/v1/settings/assistant,POST /api/v1/settings/assistant/test(owner),GET/PUT /api/v1/me/assistant,GET /api/v1/me/assistant/usage, andPOST /api/v1/assistant/turn(signed-in sessions only; streams server-sent events)./api/v1/syncaccepts the op device prefixassistant:; history entries from it carrydevice_label: "Assistant". - Dependency:
github.com/anthropics/anthropic-sdk-go(the server binary grows by about 5 MB; see the change's design notes). otfor agents and automation (CLI):--jsonoutput is documented with JSON Schemas andoutput_version1 (ot version --json);ot schema(orot help --json) prints every command, flag, exit code, error code and output schema, offline;--dry-runon every write command resolves references and prints the requests it would send without writing;--idempotency-keyonot addandot project addmakes retries return the same entity;ot done/ot reopenon a task already in that state reportunchangedand send nothing, andot editwith unchanged values sends nothing;--read-only(or the optionalOPENTODO_READ_ONLY=1) refuses every write before any request;otrefuses writes locally forreadtokens and outside the allowed routes foragenttokens;ot auth statusshows the token's scope and whether assistant writes are allowed. With--json, errors carry an additiveexitfield and stderr gets oneot:line;done/reopen/rmaddrefandstatusto each task.- Agent skill file.
ot skillprintsSKILL.md, which teaches an agent to useotsafely (agent token,--json, dry runs, idempotency keys, exit codes, task text as untrusted data). Each release ships it asopentodo-agent-skill.zip. Idempotency-KeyonPOST /api/v1/{collection}: the entity and op ids derive from your account, the collection and the key, so a retried create returns 200 with the stored entity andIdempotent-Replayed: true; the same key with other content answers 409idempotency_conflict. Keys never cross accounts and need no storage. (API)agenttokens on REST content routes: creating tasks, projects, labels and comments, editing tasks, renaming or recolouring projects and trashing the token's own tasks, under the same two switches, limits,agent.writeaudit, history label and encryption rules as the MCP write tools (they run through the same tool registry). Every other REST write still refuses them. (API)GET /api/v1/megains an additiveauthobject:method,scope,token_id,token_nameand, foragenttokens,writes_allowed/writes_denied. Never the secret. (API)- AI assistants can make changes, when you allow it. The MCP endpoint gains ten write tools:
create_tasks,update_tasks,complete_tasks,reopen_tasks,move_tasks,schedule_tasks,create_project,create_label,add_commentandtrash_tasks. They appear only when the owner turns on AI assistants may make changes, you turn on Allow AI assistants to make changes to my tasks (both off by default, in Settings → AI assistants), and the assistant uses a newagenttoken. Full-access and read-only tokens keep read-only tools. Every change goes through the op log, syncs and converges like any edit, shows in history as "AI assistant (token name)", is recorded in the audit log asagent.write(no content), and can be undone. Nothing is ever deleted permanently;trash_tasksonly accepts tasks the same token created; assistant comments notify no one. Limits: 50 items per call, 30 write calls per minute and 1,000 changed items per day per token. For end-to-end encrypted accounts, tools that would write readable text are not offered (encryption_enabled); scheduling, completing and moving still work. (AI assistants) agenttoken scope: refused on every REST write likeread, except the agent content routes above (it cannot create tokens, change settings or sync raw operations). Create one in Settings → AI assistants, Settings → API tokens, or withot token create <name> --scope agent;ot token listshows each token's scope.GET/PUT /api/v1/me/agent-access(your own switch; sessions and full tokens only) andwrites_enabledon/api/v1/settings/mcp. The optionalOPENTODO_MCP_WRITES=truepresets the owner's switch on first start.- New internal package
internal/agenttools: one registry of agent tools (definitions, validation, planning, limits, permission gate), to be shared with the planned in-app assistant. - Coolify one-click template, ready to submit upstream.
deploy/coolify/opentodo.yamlnow follows Coolify's current template conventions:SERVICE_URL_OPENTODO_8080, the0.2image line, a/datavolume and a/opentodo healthcheckhealth check. It also lists the useful optional variables with their defaults (includingOPENTODO_TRUSTED_PROXIESand email to task), because Coolify passes a variable from its UI only when the compose file references it. An explicitOPENTODO_BASE_URLwins over Coolify's generated URL.deploy/coolify/upstream/mirrors Coolify's repository paths and holds a draft docs page, the pull request text and step-by-step submission notes. Until the template is listed in Coolify, paste it as a Docker Compose resource. (Coolify) scripts/coolify-template-check.mjs(inmake checkand CI) fails when the template breaks those conventions, names anOPENTODO_*variable the server does not read, lags behind the latest release, or differs from its upstream copy.make coolify-smokestarts the image the way Coolify does and checks the health check, the owner setup, closed sign-up, the https base URL and data surviving a redeploy.
Changed
- An invalid
OPENTODO_TRUSTED_PROXIESentry no longer stops the server. A typo such ascloudflarused to fail startup, so the health check failed and Coolify rolled the deploy back with the cause only in the container log. Now the server starts, ignores the invalid entries (keeping the valid ones; with none valid it behaves as unset, so trust is never widened), logs one error line naming them with a "did you mean cloudflare?" hint, and shows the owner a Server configuration warning at the top of Settings and Settings → Administration. Entries may also be separated by spaces.GET /api/v1/megains an additiveconfig_warningsarray for the owner's session only. Every other invalid variable still stops startup. (Behind a reverse proxy) - Settings has sections. Instead of one long page,
/settingslists the sections with a line on what each holds, and each opens on its own page: Account, General, Notifications, Data, Capture, Integrations, AI and, for the owner and admins, Administration (/settings/account,/settings/general, …). Wide screens keep the section list beside the content; phones get a back link. Every setting works as before, Help links open the same guides, and/settings/importis unchanged. The user guide now names the section, for example Settings → AI → AI assistants. The single sign-on link flow returns to/settings/account. (Settings) - MCP read results mark text people wrote: titles, descriptions, names and comment bodies now sit inside an
untrusted_textobject, every result carries a_noticetelling the assistant to treat that text as data, tasks of shared projects carryshared: trueand commentsauthor_is_you. Label ids are listed aslabel_idsnext to the names. The tool definitions themselves are unchanged. (AI assistants) /api/v1/syncrefuses operations from devices whose id starts withagent:orcaldav:(400invalid_op): those ids are reserved for the server's own writers.- API requests with a token scope this server does not know are refused on every write route (previously only
readwas). - Migration
0026adds two side tables (api_token_agent,user_agent_access) and is applied automatically at startup. Rolling back to an older version is safe:agenttokens then act as read-only tokens. - Images built from source by Coolify report
dev+<commit>instead ofdevwhen the application's Source commit availability is Available during build (the Dockerfile readsSOURCE_COMMIT). - Coolify guide: which resource types roll and which recreate, why plain Restart and API deploys keep a cached image, first-visit owner setup, domains and passkeys, Cloudflare (proxy and Tunnel, Browser Cache TTL), and Coolify volume backups.
Fixed
- A reminder that became due while a dialog, menu or popover was open took the keyboard focus out of it. It is still announced, but focus now stays where you are.
- Phones could keep running the previous release after an update when a CDN such as Cloudflare sat in front: it cached the service worker script for hours. Service worker scripts are now sent with
Cache-Control: no-store(andCDN-Cache-Control: no-store).
Your to-do list, on your own server.
Try the demo in your browser. When you like it, install OpenTodo on your server and import your tasks.