A compact, shared task board for Hermes Desktop and Hermes agents. Todo keeps planning position, workflow status, and deadlines independent while using one profile-scoped SQLite database.
- Planning: Now, Today, Later (with one open Now task)
- Status: Open, Waiting, Blocked, Done
- Inbox capture that never claims Now without an explicit Start now action
- Durable brief, next action, closure condition, waiting/blocking context, review date, owner, approval, and bounded artefact/evidence fields
- One Board view with explicit Now/Today/Later planning, due-date grouping, and P1-P4 priority
- Append-only machine-readable task history and truthful closure notes/evidence
- Work with Hermes persists the linked stored-session ID and resumes it instead of creating a duplicate
- All-day or timed due dates, 5–480 minute estimates, projects, priorities P1–P4, legacy recurrence notes, and deterministic recurrence occurrences
- Agent-writable
hermes todoCLI and namespaced REST backend with task reads, search, agenda, backward-compatible board mutation envelopes, compact opt-in results, and revision conflicts - Optional one-time import from plugin-local legacy board storage or a JSON file
Keep task titles concise: they are the board label, not the handover document. Use the task's durable fields for the context that must survive into a new work session:
- Brief and decisions for background, constraints and conclusions already reached
- Next action for the smallest live move
- Closure condition for what proves the task is complete
- Artefacts for meeting notes, source pages, files or other links to inspect
- Waiting/blocking context when progress depends on another person or condition
Work with Hermes includes those work-relevant fields in the initial prompt.
It deliberately does not copy private sourcePayload metadata or append-only
closure history into the prompt. The title can therefore stay readable while
the session still receives the useful handover.
desktop-plugin/hermes-todo/plugin.js Desktop plugin (plain ESM)
server-plugin/hermes-todo/ Standalone Hermes server plugin
dashboard/plugin_api.py Namespaced FastAPI router
dashboard/manifest.json
hermes_todo_store.py SQLite authority
cli.py CLI implementation
tests/ stdlib unittest suite
ARCHITECTURE.md architecture and privacy boundary
Hermes Todo has two independently installed components. Install the server plugin first, then the Desktop plugin, into one explicitly selected Hermes profile. Do not rely on Hermes' sticky active profile.
The manifests in this source tree identify the v0.2.0 product slice. The versioned download examples below intentionally remain on the last published v0.1.2 release; do not substitute an unpublished v0.2.0 archive or checksum.
If your agent can install Hermes plugins, tell it:
Install Hermes Todo v0.1.2 from https://github.com/DanBennettUK/hermes-todo/releases/tag/v0.1.2 into the `default` Hermes profile. Use the release bundle, install both the server and Desktop components, and verify that the Todo pane reports Shared with Hermes.
Replace default with the name of your selected Hermes profile when needed.
The manual steps below explain the same installation if you prefer to run it
yourself.
Set the target once for every command below. Use default literally for the
default profile, or replace it with a named profile:
PROFILE=default
PROFILE_HOME="$(dirname "$(hermes --profile "$PROFILE" config path)")"PowerShell equivalent:
$HermesProfile = "default"
$ProfileHome = Split-Path (hermes --profile $HermesProfile config path)hermes --profile "$PROFILE" plugins install --no-enable \
DanBennettUK/hermes-todo/server-plugin/hermes-todoThe GitHub subdirectory installer copies the plugin into the profile. It does
not retain a Git checkout, so later upgrades use plugins install --force
rather than plugins update. The current Hermes installer also clones the
repository's default branch; it does not pin subdirectory installs to a release
tag. This is a convenience install from current main, not an immutable release
install. Review the copied files and confirm
$PROFILE_HOME/plugins/hermes-todo/plugin.yaml before enabling them:
hermes --profile "$PROFILE" plugins enable hermes-todoFor a reproducible install, use the versioned release bundle and its published SHA-256 checksum instead. The bundle contains both components from one tag.
Download both hermes-todo-v0.1.2.zip and
hermes-todo-v0.1.2.zip.sha256 from the release, then verify and unpack them:
sha256sum -c hermes-todo-v0.1.2.zip.sha256
python3 -m zipfile -e hermes-todo-v0.1.2.zip .
BUNDLE_DIR="$PWD/hermes-todo-v0.1.2"On Windows, compare Get-FileHash hermes-todo-v0.1.2.zip -Algorithm SHA256
with the published .sha256 value, then install with:
Expand-Archive -Path hermes-todo-v0.1.2.zip -DestinationPath .
$BundleDir = Join-Path $PWD "hermes-todo-v0.1.2"
New-Item -ItemType Directory -Force (Join-Path $ProfileHome "plugins")
New-Item -ItemType Directory -Force (Join-Path $ProfileHome "desktop-plugins")
Copy-Item -Recurse (Join-Path $BundleDir "server-plugin/hermes-todo") `
(Join-Path $ProfileHome "plugins/hermes-todo")
Copy-Item -Recurse (Join-Path $BundleDir "desktop-plugin/hermes-todo") `
(Join-Path $ProfileHome "desktop-plugins/hermes-todo")
hermes --profile $HermesProfile plugins enable hermes-todoReview the unpacked source, then copy both components into the explicit profile and enable the server plugin:
mkdir -p "$PROFILE_HOME/plugins" "$PROFILE_HOME/desktop-plugins"
cp -a "$BUNDLE_DIR/server-plugin/hermes-todo" \
"$PROFILE_HOME/plugins/hermes-todo"
cp -a "$BUNDLE_DIR/desktop-plugin/hermes-todo" \
"$PROFILE_HOME/desktop-plugins/hermes-todo"
hermes --profile "$PROFILE" plugins enable hermes-todoCopy the complete desktop-plugin/hermes-todo directory from the same source or
release bundle to:
$PROFILE_HOME/desktop-plugins/hermes-todo/plugin.js
The destination directory, Desktop plugin ID, server plugin name, API
namespace, and data directory must all remain hermes-todo. The visible label
is Todo.
- In Hermes Desktop, select the same profile used for the server-plugin installation.
- Run Reload desktop plugins from the command palette. This reloads the JavaScript UI only.
- If that profile already had a Desktop backend running before the server plugin was installed or enabled, open Desktop connection settings and choose Save and Reconnect. Hermes Desktop invalidates the cached backend, waits for it to exit, and starts a fresh route-owning process. Merely reloading the renderer or reconnecting to the same process is insufficient.
- Open Todo and require Shared with Hermes before using it. That successful route plus the matching CLI count below is the activation proof.
Do not restart the messaging gateway for a Todo API 404. The gateway and the Desktop profile backend are separate processes; restarting the gateway does not reload Desktop plugin routes.
The Todo pane should report Shared with Hermes and display the same board as the CLI. To verify the count without printing task contents:
hermes --profile "$PROFILE" todo list | python3 -c \
'import json,sys; d=json.load(sys.stdin); print({"tasks": len(d["tasks"]), "revision": d["revision"]})'Before an upgrade, preserve an executable copy of both installed components. The data directory is separate and must not be copied, moved, or deleted:
STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
BACKUP_DIR="$PROFILE_HOME/backups/hermes-todo-code-$STAMP"
mkdir -p "$BACKUP_DIR"
cp -a "$PROFILE_HOME/plugins/hermes-todo" "$BACKUP_DIR/server"
cp -a "$PROFILE_HOME/desktop-plugins/hermes-todo" "$BACKUP_DIR/desktop"For the mutable convenience path, disable the plugin, replace the copied server code, review it, then enable it again:
hermes --profile "$PROFILE" plugins disable hermes-todo
hermes --profile "$PROFILE" plugins install --force --no-enable \
DanBennettUK/hermes-todo/server-plugin/hermes-todo
hermes --profile "$PROFILE" plugins enable hermes-todoReplace the Desktop hermes-todo directory from the same reviewed source. Run
Reload desktop plugins, then use Save and Reconnect whenever Python
backend code changed or the server plugin was newly enabled. Require Shared
with Hermes and confirm the CLI count before resuming writes.
hermes plugins update hermes-todo is not supported for this GitHub
subdirectory install because the installed copy has no .git directory.
The reinstall command fetches the current default branch, not an immutable
release tag.
An error such as Headless backend (hermes serve): Web UI disabled does not
mean Todo needs the browser dashboard. It means the authenticated request
reached a Desktop backend that did not mount the Todo route.
- Confirm
hermes-todois enabled in the same profile selected in Desktop:hermes --profile "$PROFILE" plugins list --plain --no-bundled. - Confirm the server manifest exists at
$PROFILE_HOME/plugins/hermes-todo/dashboard/manifest.json. - In Desktop connection settings, choose Save and Reconnect so Desktop stops the cached backend and starts a fresh one for that profile.
- Run Reload desktop plugins and reopen Todo.
Do not rerun an import or migration to troubleshoot this error. Database presence and CLI success do not prove an already-running Desktop backend loaded the API route.
If Desktop still sees a backend process but cannot connect to it after Save
and Reconnect, do not kill arbitrary Hermes processes or force-restart the
messaging gateway. Capture the Desktop error and run
hermes logs gui -f; a process that remains alive after losing its listener is
a Hermes Desktop lifecycle fault, not a Todo database fault.
Confirm the Desktop files are at the exact path above, the folder remains
hermes-todo, and the plugin uses only the Hermes SDK, React, the JSX runtime,
and its inline re-entry prompt formatter. Disk plugins must remain a single
plain ESM file because Hermes Desktop evaluates them from a blob URL; relative
imports such as ./work-prompt.mjs cannot resolve there. Then run Reload
desktop plugins.
Disable the Desktop plugin in Settings → Plugins, then disable the server plugin in the explicitly selected profile:
hermes --profile "$PROFILE" plugins disable hermes-todoDo not delete $PROFILE_HOME/hermes-todo/todo.sqlite3. Plugin code and data are
separate, so the board remains available for re-enabling or diagnosis.
To roll back, restore the exact pre-upgrade code snapshot while retaining the failed version for diagnosis:
FAILED_SUFFIX="$(date -u +%Y%m%dT%H%M%SZ)"
mv "$PROFILE_HOME/plugins/hermes-todo" \
"$PROFILE_HOME/plugins/hermes-todo.failed-$FAILED_SUFFIX"
mv "$PROFILE_HOME/desktop-plugins/hermes-todo" \
"$PROFILE_HOME/desktop-plugins/hermes-todo.failed-$FAILED_SUFFIX"
cp -a "$BACKUP_DIR/server" "$PROFILE_HOME/plugins/hermes-todo"
cp -a "$BACKUP_DIR/desktop" "$PROFILE_HOME/desktop-plugins/hermes-todo"
diff -qr "$BACKUP_DIR/server" "$PROFILE_HOME/plugins/hermes-todo"
diff -qr "$BACKUP_DIR/desktop" "$PROFILE_HOME/desktop-plugins/hermes-todo"
hermes --profile "$PROFILE" plugins enable hermes-todoOn Windows, use Copy-Item -Recurse for code snapshots and Move-Item to
retain the failed version; keep the same source and destination directories.
Then run Reload desktop plugins, choose Save and Reconnect, require Shared with Hermes, and verify the CLI count before resuming writes. A versioned release bundle can be restored in the same way after verifying its published SHA-256 checksum.
hermes --profile "$PROFILE" todo list
hermes --profile "$PROFILE" todo capture "Prepare release notes" --brief "Decision log and context"
hermes --profile "$PROFILE" todo start TASK_ID --expected-revision REVISION
hermes --profile "$PROFILE" todo agenda --timezone Europe/Amsterdam
hermes --profile "$PROFILE" todo show TASK_ID --history
hermes --profile "$PROFILE" todo search "release" --owner Dan
hermes --profile "$PROFILE" todo wait TASK_ID --waiting-on "Reviewer" --review-date 2026-08-12
hermes --profile "$PROFILE" todo block TASK_ID --blocker "Approval required"
hermes --profile "$PROFILE" todo done TASK_ID --closure-note "Verified locally" --evidence tests/test_store.py
hermes --profile "$PROFILE" todo add "Monthly close" --due 2026-08-31 --recurrence-rule monthly --recurrence-timezone Europe/Amsterdam
hermes --profile "$PROFILE" todo update TASK_ID --next-action "Run the focused suite" --artefact tests/test_store.py
hermes --profile "$PROFILE" todo session-link TASK_ID STORED_SESSION_ID --expected-revision REVISION
hermes --profile "$PROFILE" todo follow-up TASK_ID "Check response" --status waiting --waiting-on Reviewer
hermes --profile "$PROFILE" todo import ./tasks.jsonRead commands and mutation commands emit explicit JSON objects. Mutations default to the backward-compatible full-board envelope with tasks and the new revision; recurrence and follow-up operations also retain their generatedTask or followUpTask metadata. Pass --compact for a result containing the changed task and any affectedTasks, generatedTask, or followUpTask. The existing --board spelling remains available as an explicit request for the default board envelope. Pass --expected-revision to protect a read/modify/write sequence; a stale write exits with code 3 and an error: "revision_conflict" object without changing the board.
Import accepts either a JSON task array or { "tasks": [...] }. Stable external records should include both source and externalId; repeated imports skip existing identities. Imported createdAt, updatedAt, and (for Done tasks) completedAt are preserved. Unknown imported fields are merged into the private, non-returned source payload without replacing an existing sourcePayload.legacyFields mapping.
The free-text recurrence field remains a display note and is never treated as executable. recurrenceRule is deterministic and supports only:
dailyweekdaysweeklymonthly(the day is clamped to the destination month's last day)every:<n>d, wherenis 1–365, for exampleevery:14d
An executable rule requires dueDate or dueAt. recurrenceTimezone is an IANA timezone and defaults to the timed due timezone or UTC. Completing an occurrence preserves it as Done and idempotently creates the next occurrence with one stable seriesId and a unique occurrenceId such as <seriesId>:2. Imports that provide recurrence identity must provide seriesId, occurrenceId, and occurrenceNumber together, with occurrenceId exactly <seriesId>:<occurrenceNumber>; when none are supplied, an executable recurrence rule creates the initial identity.
The namespaced API exposes GET /board, GET /tasks, GET /tasks/{id}, GET /tasks/{id}/history, and GET /agenda. Mutations default to the backward-compatible full-board envelope. Add ?envelope=result to opt into the compact mutation result; ?envelope=board remains a compatible explicit board request. Mutation bodies accept expectedRevision; delete accepts it as a query parameter. Conflicts return HTTP 409 with expectedRevision and currentRevision.
Agenda conversion interprets a naive dueAt in the task's dueTimezone before converting it to the requested agenda timezone; invalid legacy timezone metadata safely falls back to the agenda timezone. A waiting task receives the waiting_review reason only when its reviewDate is today or overdue.
Dedicated operations link or idempotently complete a work session, idempotently generate the next recurrence, and atomically complete a task with a follow-up. Omitting closure fields from complete-with-follow-up preserves existing closure data; explicit null or empty values clear it. They remain local Todo state changes: a closure note or evidence path does not claim that an external delivery was verified.
Hermes Desktop disk plugins run with the Desktop renderer's authority; loader
error isolation is not a security sandbox. Review plugin.js before installing
it and install only source you trust. A published checksum can confirm package
bytes, but it cannot make unreviewed code safe.
The namespaced REST routes inherit authentication and access controls from the Hermes host; the plugin does not implement a second authentication layer or expose a separate listener.
Imported sourcePayload metadata is retained only in the profile-local SQLite database, is limited to 65,536 serialized UTF-8 bytes (64 KiB) per task, and is never included in board responses. Review imports before loading them because payload metadata can still contain private source information. See SECURITY.md for vulnerability reporting.
No build step or third-party runtime dependency is required for the store/CLI. The API is loaded inside Hermes, which supplies FastAPI and Pydantic. Python's standard-library timezone database must contain the selected IANA recurrence/agenda timezone.
PYTHONPATH=server-plugin/hermes-todo python3 -m unittest discover -s tests -v
python3 -m compileall -q server-plugin tests
node --check desktop-plugin/hermes-todo/plugin.jsDesktop plugin files are uncompiled ESM: no JSX, bundler, or imports beyond the Hermes SDK, React, and react/jsx-runtime.
See ARCHITECTURE.md for storage and privacy details.