All plugins

kanban-task-threads

Give every Hermes kanban task a live Discord forum thread with a rewritten status card and append-only event log. Disclosure — sends task and lifecycle data to the configured Discord webhook (no host allowlist). Cards and event replies use built-in or operator-configured templates and may include identifiers, titles, status, assignment, branch and workspace metadata, relationships and blocking details, comments, event actors, reviewers, reasons, summaries, and configured dashboard links; the comment excerpt length is configurable. Fixed prerequisite announcements additionally send the prerequisite task ID, title, assignee, and up to 140 characters of its body and are not template-configurable. The absolute workspace path is off by default and requires explicit opt-in. Reads the kanban database read-only and stores thread mapping state under <kanban_home>/kanban/plugins/kanban-task-threads/. With an optional existing publisher-profile Discord bot token, reads forum and thread metadata; creates or repairs managed status tags and emojis; replaces applied tags on plugin-managed threads; renames threads; and archives or unarchives them. Discord fetches webhook avatars from the configured HTTPS asset origin. Disclosure — the avatar asset origin defaults to the author-hosted https://diegomarino.github.io/kanban-task-threads (disable with avatars_enabled: false). With the bot token the periodic metadata audit also lists the guild's active threads (filtered to the forum) and may rewrite the forum's available-tag list to add the 11 managed status tags (requires Manage Channels).

diegomarino5Automationv0.4.1Updated 1 month ago

About

Give every Hermes kanban task a live Discord forum thread with a rewritten status card and append-only event log. Disclosure — sends task and lifecycle data to the configured Discord webhook (no host allowlist). Cards and event replies use built-in or operator-configured templates and may include identifiers, titles, status, assignment, branch and workspace metadata, relationships and blocking details, comments, event actors, reviewers, reasons, summaries, and configured dashboard links; the comment excerpt length is configurable. Fixed prerequisite announcements additionally send the prerequisite task ID, title, assignee, and up to 140 characters of its body and are not template-configurable. The absolute workspace path is off by default and requires explicit opt-in. Reads the kanban database read-only and stores thread mapping state under <kanban_home>/kanban/plugins/kanban-task-threads/. With an optional existing publisher-profile Discord bot token, reads forum and thread metadata; creates or repairs managed status tags and emojis; replaces applied tags on plugin-managed threads; renames threads; and archives or unarchives them. Discord fetches webhook avatars from the configured HTTPS asset origin. Disclosure — the avatar asset origin defaults to the author-hosted https://diegomarino.github.io/kanban-task-threads (disable with avatars_enabled: false). With the bot token the periodic metadata audit also lists the guild's active threads (filtered to the forum) and may rewrite the forum's available-tag list to add the 11 managed status tags (requires Manage Channels).

kanban-task-threads

A Hermes plugin that gives every kanban task a Discord forum thread of its
own
: the first post is a live status card rewritten in place, and the replies
are the append-only log. The post body answers "what is happening now?"; the
thread answers "how did we get here?".

Discord forum with managed status tags, task posts, a live status card and
actor-signed thread updates

Webhook replies can also carry a Phosphor avatar selected by message type:

Discord preview of the approved Phosphor message-state avatars: default,
commented, blocked, unblocked, review requested, changes requested, completed,
gave up, crashed, timed out, reclaimed and archived

The card re-renders from board state on every change (status, block reason,
assignee, prerequisites, elapsed time via Discord's live <t:…:R> timestamps).
Each reply is signed with the profile that caused it. The card itself is
signed with the webhook's own name — deliberately: a webhook message's
username is fixed at creation, so signing the card with an assignee would
freeze the first one forever (see docs/transport.md).

Requirements

  • Hermes 0.20+ with the kanban board in use.
  • A Discord forum channel and a webhook bound to it. The webhook is
    write-only and bound to one channel: a leak costs only the ability to post
    there.
  • Optionally, the selected publisher profile's existing Discord bot token.
    Do not create a dedicated bot for this plugin. Its integration-managed role
    needs View Channel, MANAGE_THREADS (shown in Discord as Manage Threads
    and Posts
    ), READ_MESSAGE_HISTORY for archived listing, and
    MANAGE_CHANNELS (Manage Channels) to create missing status tags. Scope
    both management permissions to this forum, not the whole server. This unlocks
    title state, tags, archiving and metadata repair. The plugin works without a
    bot token and never requires one.

Discord role permission toggle labelled Manage Threads and Posts; this UI permission corresponds to the MANAGE_THREADS API permission

Discord creates and manages this integration role for the bot. Grant the
permission to that role; it is not a role you manually assign to people.
If you do not grant Manage Channels, create all managed tags manually before
the first publishing pass with the documented emojis; the plugin will reuse
them and will not need that permission while the full vocabulary remains
present and unchanged.

Install

The reviewed catalog entry is the normal install path:

hermes plugins install kanban-task-threads
hermes plugins enable kanban-task-threads   # opt-in allow-list

A direct GitHub install remains available for unreleased revisions, but Hermes
treats it as an unreviewed community source and scans the whole repository —
including tests, CI and documentation — before installing it:

hermes plugins install diegomarino/kanban-task-threads
hermes plugins enable kanban-task-threads

If a future revision reports CAUTION, review every finding before repeating
the command with --force; that flag cannot override a DANGEROUS verdict.
Do not disable install-time scanning. Prefer the catalog name and its reviewed,
pinned commit for normal installations.

Set the secret in your profile's environment (through your secret manager —
e.g. a 1Password op:// reference — never a literal in config):

KANBAN_TASK_THREADS_WEBHOOK_URL=https://discord.com/api/webhooks/…/…
KANBAN_TASK_THREADS_BOT_TOKEN=…        # optional

Then restart the gateway — a gateway holds its plugin set from startup.
Confirm with hermes plugins list that the source column reads user.

The forum channel is never configured: the plugin asks the webhook where it
posts (GET on the webhook returns its channel_id). One credential, one
source of truth.

Naming the forum is yours to do — the plugin discovers channels, it never
creates or names them. The binding between a board and a forum is the pair
(board setting, webhook secret), not any name. Suggested convention: name
the forum after the board's slug (#tasks-default, #tasks-web), so a human
reading the channel list can tell which board publishes where. One deployment
serves one board; point each board's deployment at its own forum's webhook.

Managed tags: with a bot token, the first pass holding the board lease
reuses or creates the exact forum tags 🔎 triage, 📋 todo, 📅 scheduled,
🟢 ready, 🏃 running, ⛔ blocked, 🙋 needs-human, 👀 review,
✅ done, 📦 archived, and ❌ failed. It also repairs missing or changed
emojis on those managed names. Existing unrelated tags are preserved.
Discord permits at most 20 forum tags, so the pass fails closed rather than
deleting anything if the combined set will not fit.

Forums that require tags: the bot path automatically uses managed
triage as the creation tag when no discord_applied_tag_ids override is
configured, then applies the task's real state. Webhook-only installations
must still set discord_applied_tag_ids, because a webhook cannot discover
or create forum tags.

Configuration

All optional, under plugins.entries.<id>.settings:

| Key | Default | Meaning |
|---|---|---|
| board | current board | Which board this instance serves |
| publisher_profile | empty | Exact ctx.profile_name allowed to publish. Empty preserves the public/single-profile default: every loaded profile is a lease candidate and the board lease elects the active publisher. A value pins publication and gives up cross-profile failover |
| reply_on | sensible set | Event kinds that earn a reply in the thread |
| include_workspace_path | false | Publish the absolute workspace path on the card (see Privacy) |
| discord_applied_tag_ids | [] | Optional creation-tag override. Required only for webhook-only operation in a forum that enforces a tag; bot mode otherwise bootstraps with managed triage |
| avatars_enabled | true | Add message-state avatars; false preserves plain webhook identities |
| avatar_base_url | official catalog | Optional HTTPS directory containing 12 custom PNGs; blank uses the official versioned catalog |
| avatar_theme | duotone | Official-catalog Phosphor weight: duotone, fill, or bold; ignored with a custom directory |
| avatar_palette | colored | Official-catalog palette: colored, black, or white; ignored with a custom directory |
| poll_seconds | 20 | Seconds between passes when no hook kicks arrive |
| stale_after_seconds | 600 | Heartbeat age after which a running task renders as stale |
| dashboard_url | — | Task page URL; may contain {task_id} and {board}. Powers the card link and each reply's trailing [#] (without it, [#] jumps to the card in-app). Links are forever: every URL you publish is frozen in Discord permanently, so use a stable name that resolves from every device you click from — never an IP (the plugin warns on literal IPs), never anything discovered at boot |
| comment_excerpt_chars | 180 | Comment text quoted in replies; 0 = author only |
| card_template, reply_templates | built-ins | Cosmetic templates (docs/configuration.md) |

Templates are rendered through a restricted formatter (flat fields only —
attribute/index traversal is rejected) and fall back to the built-ins on any
error. What is not configurable, by design: which events publish, when the
card re-renders, idempotency, retry policy, truncation, and
allowed_mentions: {"parse": []} on every call.

Message avatars

Avatars are author identity, not message media: the plugin sets Discord's
avatar_url when creating the starter or a reply. It does not add attachment
images, embed thumbnails, or image embeds. The starter always uses default;
replies cover commented, blocked, unblocked, review_requested,
changes_requested, completed, gave_up, crashed, timed_out,
reclaimed, and archived. Unknown future kinds fall back to default.

Discord may visually group consecutive webhook messages that have the same
username even when their avatar_url differs. With avatars enabled, reply
identities therefore include the type: {profile_id} · {message_type} (for
example, profile_wester · blocked). Events without an attributable actor use
system · {message_type}. This keeps every type's avatar visible while
preserving the real profile id whenever it fits. Discord caps webhook names at
80 characters, so an unusually long profile id is marked with … and shortened
just enough to retain the complete message type. When avatars are disabled,
usernames remain unchanged and system observations continue using the webhook's
own identity.

With no avatar settings, the plugin uses the official duotone/colored
catalog. The repository includes 96 px PNGs for all three themes and palettes
under pages/v1/, plus their Phosphor 2.1.1 SVG sources, render manifest, and
license under assets/avatars/. colored is the approved semantic palette;
black uses a black glyph on a white circle and white uses a white glyph on
a black circle. Phosphor's native 20% duotone layer produces the secondary grey
without inventing a second icon color.

The checked-in workflow publishes pages/ with GitHub's official Pages actions
when that directory changes on main. The built-in URL contract is:

https://diegomarino.github.io/kanban-task-threads/
  v1/{theme}/{palette}/96px/{message_type}.png

The bundled manifest selects v1; it is not a user setting. A significant
visual redesign moves the plugin to v2, while v1 remains published for old
installations and messages.

For custom artwork, avatar_base_url is the final directory containing
default.png, commented.png, blocked.png, unblocked.png,
review_requested.png, changes_requested.png, completed.png, gave_up.png,
crashed.png, timed_out.png, reclaimed.png, and archived.png. The plugin
appends only the filename—never its own version, theme, palette, or size:

avatar_base_url: https://assets.example.com/hermes-avatars
https://assets.example.com/hermes-avatars/blocked.png

The custom directory must be public HTTPS without authentication. It needs no
manifest. Theme and palette settings are ignored in custom mode; choose a
different collection by changing the directory URL. Invalid cosmetic settings
fall back or disable only avatars, never task publication. Set
avatars_enabled: false to opt out explicitly. Full operational details are in
Avatar customization and self-hosting.

The renderer for the official catalog requires rsvg-convert and performs no
network access:

python3 scripts/render_avatars.py

The runtime never hotlinks Phosphor, uploads assets, probes an origin, or
creates hidden Discord messages to host files. See
ADR-0015.

Privacy and egress — read before enabling

Everything this plugin publishes leaves your machine permanently and is
visible to everyone who can read the channel:

  • Task titles, block reasons, completion summaries and failure
    text are arbitrary agent output and may echo secrets or stack traces. They
    are published because they are the feature — length-capped, mentions
    neutralized — but they are an egress path. Point the webhook at a channel
    with an audience you trust.
  • workspace_path contains your OS username and project layout. Off by
    default
    ; opt in with include_workspace_path.
  • Comment text is excerpted into replies (length-capped, ADR-0011);
    set comment_excerpt_chars: 0 if comment bodies must never leave the board.
  • When a prerequisite thread opens, its dependency announcement includes the
    first 140 characters of that task's body. This path currently has no opt-out;
    avoid sensitive task bodies when dependency announcements are enabled.

Failure behavior

Discord being down never loses an event: the plugin consumes the board's own
event log past a durable cursor, so it resumes where it left off after any
crash or restart. Unreachable configurations degrade to a no-op with one log
line instead of queuing work that can never be sent. A deleted post is
respected (the plugin stops publishing for that task rather than re-creating
it), and re-pointing the webhook freezes old posts instead of corrupting them.
Details: docs/failure-policy.md.

Diagnostics go through the standard Python logger (kanban-task-threads
namespace): warning for transient retries, error for anything needing an
operator.

Limitations, honestly

  • Comment text is excerpted, not full — replies quote up to
    comment_excerpt_chars (default 180) with a link to the full text;
    set it to 0 to publish only who commented (ADR-0011).
  • Latency is seconds to poll_seconds, not instant: hooks are best-effort
    kicks; correctness rides the durable cursor.
  • The thread name is frozen for webhook-only deployments (it carries the
    task id on purpose); a bot token unlocks renames.
  • The status tag owns the thread's tags when the bot path is active: a
    manually applied tag will not survive the next automated retag
    (docs/transport.md documents the tradeoff).
  • Metadata audit is deliberately bounded to all active guild threads plus
    the forum's latest 25 public archived threads. Older archived posts are not
    scanned until they re-enter that window.
  • Replies are at-least-once across a crash between send and cursor write;
    a rare duplicate reply is possible. Post creation is strictly guarded
    instead — ambiguous outcomes wait for an operator (reconcile verbs).
  • One deployment serves one board and one forum. Multi-forum routing is
    deliberately not built (ADR-0010).

This project is not affiliated with or endorsed by Discord Inc. or
Nous Research.

Uninstall

hermes plugins disable kanban-task-threads
hermes plugins remove kanban-task-threads

remove deletes the directory but does not touch plugins.enabled — run
disable too. The task→post mapping is left behind deliberately (it lives
with the board, not the plugin); posts are never deleted.

Development

./scripts/sandbox test      # unit suite (needs uv; pytest runs only through it)
./scripts/sandbox up        # disposable Hermes home — never touches a real one
./scripts/sandbox task      # walk a task through a realistic lifecycle
./scripts/sandbox consume   # run the consumer against it, printing, no network
./scripts/sandbox doctor    # load the plugin through the real runtime, offline

Design rationale lives in the ADRs; the map from design to code
in ARCHITECTURE.md; topic deep-dives in docs/; releases in
CHANGELOG.md (process: RELEASING.md); vulnerability
reports: SECURITY.md.

A second transport?

Discord is the first transport, not the only conceivable one. The transport
seam is five operations plus a capability declaration, and a Telegram mapping
exists on paper — along with what it would lose (live timestamps, per-message
identity, the webhook credential model). Read
docs/transport.md before proposing one.

README from the reviewed commit 4672846; it updates when the author re-pins.