Matrix bot for task queue management on forge. Accepts text commands in a designated Matrix room, handles custom widget events from the task queue dashboard widget, and keeps a set of pinned, self-updating status boards — one per agent — plus a single daily morning brief.
The bot runs as a PM2 always-on service (matrix-task-queue-bot) using the matrix-nio Python client. It reads task YAML files directly from ~/.claude/task-queue/ — no HTTP dependency on task-queue-mcp for queries.
Four subsystems run concurrently:
- Text command handler — responds to
!commands in the task queue room - Widget event handler — processes custom
com.helmforge.task.*room events from the Matrix widget - File watcher (watchdog) — monitors
~/.claude/task-queue/*.ymland, on any change, coalesces a single refresh of the live boards - Daily digest scheduler — posts one dated "morning brief" per day at
DIGEST_HOUR(local time)
The Task Queue room is a passive status surface: the boards edit in place silently (via m.replace) and do not notify. The only notifying message is the once-a-day morning brief.
All commands must be sent to the configured task queue room (MATRIX_ROOM_TASK_QUEUE).
| Command | Description |
|---|---|
!queue |
List all non-terminal tasks (excludes completed / failed) |
!queue <agent> |
List tasks for a specific agent |
!task <id> |
Show task detail (accepts full UUID or 8-char prefix) |
!task start <id> |
Launch agent session in review mode (plan permissions, agent summarizes then waits) |
!task run <id> |
Launch agent session in auto mode (agent claims and executes) |
!task approve <id> |
Set task status to approved (actor: operator) |
!help |
Show command reference |
Task IDs accept either full UUIDs or 8-character prefixes. Short IDs must be at least 8 characters.
The bot handles custom Matrix room events sent by the task queue widget (matrix-task-queue-widget). Read-only events are open to all room members; mutating actions require the sender to be in AUTHORIZED_MXIDS.
| Event type | Auth required | Action |
|---|---|---|
com.helmforge.task.list |
No | Returns filtered task list via com.helmforge.task.data |
com.helmforge.task.detail |
No | Returns single task via com.helmforge.task.response |
com.helmforge.task.start |
Yes | Launches headless agent session |
com.helmforge.task.approve |
Yes | Sets task status to approved |
Responses are sent as custom room events (com.helmforge.task.response / com.helmforge.task.data). The widget correlates responses via request_id in the event content.
The file watcher (watchdog, non-recursive) monitors ~/.claude/task-queue/ for any change — creation, modification, deletion, or move (archival). All events are collapsed by a single coalesce timer (BOARD_COALESCE_SEC, default 2s) into one refresh pass.
Each refresh rebuilds one board per agent in BOARD_AGENTS (plus any other agent seen in the queue, appended lazily) from a fresh directory scan:
- Each board lists that agent's non-terminal tasks (
submitted,approved,pending-approval,in-progress;completed/failed/cancelledare excluded), sorted by priority → status → age. - Columns: ID · Priority · Status · Type · Summary · Age. Header shows
AGENT (n); an agent with no open tasks showsAGENT (0) — ✔️ no open tasks. - The board message is edited in place via an
m.replacerelation, so updates are silent (no notification) and the message keeps a stable event ID. Boards whose meaningful content is unchanged are skipped to avoid churn (theupdated HH:MMfooter is a wall-clock stamp of the last real change, not a live clock). - All boards are pinned in one
m.room.pinned_eventsstate event so they read top-to-bottom inBOARD_AGENTSorder. Pinning requires the bot to hold a state-event power level (PL 50) in the room; if it can't, pinning degrades to a logged no-op and the boards still work unpinned.
Board message event IDs are persisted to ${STATE_DIR}/boards.json (an agent → event_id map) so the bot re-edits the same messages across restarts. Delete boards.json to force a clean re-post.
Once a day at DIGEST_HOUR (local time, default 05:00) the bot posts a fresh message (not an edit) — the one message in the room that notifies. It is a dated digest of all non-completed tasks grouped by agent (Morning brief — YYYY-MM-DD · N non-completed across M agents).
A stamp file ${STATE_DIR}/digest-stamp records the last-sent date to guard against a double-send on restart. On startup the scheduler arms for the next occurrence of DIGEST_HOUR; it does not send a catch-up brief for a hour already passed.
| Variable | Required | Default | Purpose |
|---|---|---|---|
MATRIX_HOMESERVER_URL |
Yes | — | Matrix homeserver (e.g. http://localhost:8008) |
MATRIX_ACCESS_TOKEN |
Yes | — | Bot access token |
MATRIX_ROOM_TASK_QUEUE |
Yes | — | Room ID for task queue commands (e.g. !task-queue:helmforge.me) |
MATRIX_BOT_USER_ID |
No | @forge-task-queue:helmforge.me |
Bot's Matrix user ID |
TASK_QUEUE_MCP_URL |
No | http://localhost:8485/mcp |
Unused at runtime (reads files directly) |
TASK_QUEUE_DIR |
No | ~/.claude/task-queue |
Task YAML directory |
AUTHORIZED_MXIDS |
No | @ted:helmforge.me |
Comma-separated MXIDs allowed to run mutating commands |
STATE_DIR |
No | ~/.local/state/matrix-task-queue-bot |
Holds boards.json (agent→event_id) and digest-stamp |
DIGEST_HOUR |
No | 5 |
Local-time hour (0–23) for the daily morning brief |
BOARD_COALESCE_SEC |
No | 2 |
Debounce window collapsing a burst of queue writes into one board refresh |
BOARD_AGENTS |
No | developer,sysadmin,research,writer,security |
Ordered set of agents to always keep a (possibly empty) board for |
MAX_BOARD_AGENTS |
No | 25 |
Hard cap on total boards; extra agents beyond it are logged and dropped (floored to BOARD_AGENTS size) |
ENV_FILE |
No | ~/.secrets/matrix-task-queue-bot.env |
Path to dotenv file |
Requires Python 3.12+.
cd ~/repos/personal/matrix-task-queue-bot
python3 -m venv venv
source venv/bin/activate
pip install -e .| Package | Purpose |
|---|---|
matrix-nio[e2e] |
Matrix client |
httpx |
HTTP client (trigger-proxy support) |
watchdog |
File system watcher |
pyyaml |
Task YAML parsing |
python-dotenv |
Env file loading |
// ecosystem.config.js excerpt
{
name: "matrix-task-queue-bot",
script: "venv/bin/matrix-task-queue-bot",
cwd: "/home/ted/repos/personal/matrix-task-queue-bot",
env: { ENV_FILE: "/home/ted/.secrets/matrix-task-queue-bot.env" },
restart_delay: 5000,
autorestart: true,
}pm2 start ecosystem.config.js
pm2 save| Mode | Permission mode | Agent behavior |
|---|---|---|
review |
plan |
Reads task, presents summary, waits for operator approval |
auto |
default |
Reads task, claims it (in-progress), executes |
Sessions are launched as detached claude subprocesses pointing at the agent's project directory. The task target_agent field determines which project directory is used.
- PM2 service:
matrix-task-queue-bot(always-on) - Env file:
~/.secrets/matrix-task-queue-bot.env - Matrix room:
#task-queue:helmforge.me - Repo:
~/repos/personal/matrix-task-queue-bot/