Repository navigation
Epic: task-monitor, a real-time view of in-flight, next and done work per repo #272
Description
Epic. The sub-issues below are the delivery plan; this body is the agreed design. It came out of a PM/user design discussion on 2026-10-20 and a builder read-through.
Why
With task-force, a ticket takes minutes rather than a day. A normal session runs
ten or more tickets, all referred to by number. The PM and the workers keep up.
The human driving the PM does not: "#262" no longer reliably means anything to
them, and nothing shows the shape of the work as a whole. They need to see
what is in flight, what is next, and what landed, each as a title plus a short
human description. Done items also need a short account of what the solution
actually was.
There is a second job. The user runs one PM per project, in several repos at
once, on both claude and kiro. They need to see at a glance which project needs
them now and which can be left alone.
Goals
- A per-repo view in three sections: In flight, Next, Done. Each
item shows its number, title, and a one- or two-line plain description. - Done items also carry a high-level summary of the solution, expandable in
place to the full PR body. - An all-repos overview with one row per repo and an attention light.
- Real-time: local events (radio mail, worker state, tab icons) show up
immediately. Forge state (PR and CI) is polled. - No model tokens spent by the monitor. Every value it shows is read
deterministically from disk, the tracker, or the forge. - Works for claude and kiro PMs, and on every tracker task-force supports (gh,
jira, notion, local). The exceptions are stated: the❓signal is claude-only
in v1, and jira/notion show a slug before the PR exists (see Data sources).
Non-goals
- Not a project-management view. The tracker owns the backlog as a whole. The
monitor's "Next" is the short-term queue only. - It does not replace the PM or drive work. It is read-only, except for jumping
to a tab and opening URLs. - It does not need a global PM. The overview reads each repo's state directly.
A cross-repo PM may come later, and the monitor must not depend on one.
Anti-goals (a failure even if it technically works)
- Calling a model to produce or summarise anything shown.
- A screen that shows GitHub's slowness: it hangs, or goes blank while it waits.
- Task-force logic duplicated in Rust, such as tracker parsing, role naming, or
radio file formats, which would drift from the bash that owns it. - The PM spending meaningful tokens maintaining the board.
- Light noise: flagging a repo for anything other than the three signals below.
Architecture (decided)
Two parts.
1. task-monitor snapshot --json (bash)
A canonical root binary (bin/task-monitor), installed like radio and
task-pm. It gathers everything for one repo, or with --all for every repo
radio knows about, into a single JSON document on stdout. It reuses the
existing modules rather than re-deriving anything:
- tracker modules in
lib/trackers/{gh,jira,notion,local}.sh, through new
tracker hooks with_default.shbodies, covering issue title and
description, the Todo fallback, and issue URLs; lib/agents/*where agent-specific state matters;- radio's on-disk state under
${TASK_FORCE_HOME:-~/.task-force}/radio/.
It is testable with bats and usable on its own, by a human or by a PM that wants
to read the board. A versioned schema ("schema": 1) is the contract with the
front end.
2. The ratatui front end (Rust)
It lives in the repo (location is the builder's call, e.g. monitor/) and is
built with cargo build --release by install.sh when cargo is on PATH. When
cargo is missing, the installer skips it and prints one line saying how to get
it. install.sh symlinks the built binary into ~/.local/bin as the user-facing
command. The user-facing name is the builder's call, but it must not collide
with the bash task-monitor snapshot command; one option is that the Rust
binary is task-monitor and the bash one is task-monitor-snapshot, called by
it. Prebuilt release binaries are a possible later step and out of scope here.
It owns the screen, the keys, and change detection, and nothing else:
- it watches the radio directory and worktree sidecars with filesystem events
and re-runs the snapshot on change, debounced; - local refresh and forge calls are split: a filesystem event never waits on
gh. The snapshot takes a flag (or the front end runs two collectors) so the
fast path reads disk only and merges with the last forge result; - it re-runs the snapshot on a slow timer (30-60s) for forge state;
- it renders the last good snapshot. A snapshot that fails or times out never
blanks the screen; it shows a small stale indicator instead.
Both machines the user works on have a Rust toolchain.
Data sources (decided unless marked)
In flight
-
What counts as in flight: a worktree whose
<worktree-base>/.<slug>.info
sidecar exists, not a fresh heartbeat. A resumed or roleless session is still
in flight. -
Workers: radio sessions whose role is
worker-<repo>-<slug>, joined to
<worktree-base>/.<slug>.infofor the issue ref and branch. Reviewers
(reviewer-<repo>-pr<N>) attach to their PR's item, not to their own row. -
Status, as a short word: implementing / PR open / under review / changes
requested / re-review / waiting merge. It is derived from the PR's existing
state plus the latest radio intent exchanged with the PM about that PR. -
Activity icon:
▶/⏸/❓, from the session file'sSTATE=
(busy / idle / awaiting) rather than by asking zellij. It also shows time since
the last state change. -
Title and description (decided, no PM writing):
gh / local jira / notion before the PR exists issue title + first paragraph of the body (gh via gh; local from thetasks/file)the ref + worker slug, e.g. PROJ-42 · kiro-universal-config. The shell has no read access to these trackers (agent-only MCP), and we deliberately add no API credentialsonce the PR exists PR title + ## SummaryPR title + ## SummaryA non-gh PR is linked to its item through its
task/<slug>head branch.
Later, only if the jira/notion pre-PR gap proves too weak in use: an optional
task-monitor describe <ref> "<line>"the PM may run when dispatching. Not
part of v1, and only acceptable if it costs the PM's context next to nothing.
Next
- The PM's queue: a small per-repo file under
~/.task-force/that the PM
writes during its normal conversation with the user, through a subcommand such
astask-monitor next 267 266 --note 267="kiro hooks first", or an equivalent
set/add/rm/clearform. It holds an ordered list of tracker refs
with optional one-line notes. Writing it is the PM's only board cost. The PM
prompts for every loadout (commands/pm.md, the kiro PM agent prompt) learn
this command and when to use it: when the user and PM agree on what comes next. - Fallback when the queue is empty: on gh, open project items in none of the
three configured statuses (in progress / in review / done), in project order,
top N. On local, thetasks/backlog in its own order. On jira/notion there
is no fallback: the section shows the queue, or says it is empty. Items
already in flight are excluded. No new tracker settings are introduced for
this.
Done
- Source: merged PRs linked to a tracker item (
Closes #Nor the tracker
equivalent), newest first, over a recent window. The builder's default: the
last 24 hours, or the last 10 items, whichever is larger. - Summary: the PR body's
## Summarysection. The worker prompt for every
loadout is changed to require that section first in every PR body: two or
three plain sentences, written for the user rather than a reviewer, about
what changed and why. For PRs without it, use the first paragraph after the
Closesline. - Expand: pressing ⏎ shows the full PR body, scrollable, in place.
- Cache: merged PRs never change, so their title, summary, body, merge SHA
and time are cached on disk and fetched once. Only new merges hit the forge.
Attention light (overview), exactly three signals
- Unread PM inbox.
mailbox/pm-<repo>/inbox/is non-empty.--alsoalias
inboxes count towards the primary PM's repo. - Review waiting merge. A
review-complete-cleanor
review-complete-with-findingsfor a PR that is still open, with no later
changes-requestedorapproved-and-mergedfrom the PM for that PR. Rebuilt
from message frontmatter (intent:,pr:) in the mailboxes, since the radio
log'ssendlines carry nopr=field. - A worker stuck on a question. A worker session of that repo in the
awaitingstate (the state behind the tab's❓︎prefix). claude-only in
v1: only the claude loadouts wireradio awaiting(PermissionRequest,
AskUserQuestion, ExitPlanMode); kiro installers wire busy/ready only. The gap
is documented, and a sub-issue investigates a kiro equivalent.
The overview row says which signal fired, in words.
Screens (decided by the user)
The repo view (default when run inside a repo):
task-force · PM claude · ✉ 1 unread 12:04 · live
─────────────────────────────────────────────────────────────────────────────
IN FLIGHT (2)
▸ #267 kiro upgraded agent-config breaks task-init/remove PR #270 re-review
Teach the hook merge and strip the new array hook shape ▶ 14m
#262 Launched tabs ignore TASK_FORCE_HOME implementing
A scratch-home probe registers in the real radio state ❓ 3m
─────────────────────────────────────────────────────────────────────────────
NEXT (PM queue)
1 #180 stale TAB_ID / ambiguous tab / .info sourcing "after #267"
2 #241 task-done unregister undone one turn later
─────────────────────────────────────────────────────────────────────────────
DONE (last 24h)
#266 task-remove hangs under bash 3.2 PR #269 · 1415b86 · 2h ago
Both whitespace checks use a grep helper instead of a bash pattern
substitution that never finished on large UTF-8 files.
─────────────────────────────────────────────────────────────────────────────
↑↓ move ⏎ expand o open PR/issue t jump to tab a all repos q quit
The all-repos overview (a, or --all). ⏎ on a row opens that repo's view:
task-monitor · all repos 12:04
● task-force ✉ 1 unread · 1 review waiting merge 2 in flight
● recommender-systems ❓ rerank-sasrec-gate is asking 1 in flight
○ other-repo idle
Keys: ↑↓ move, ⏎ expand or enter, o open the PR or issue URL, t jump to
the item's zellij tab (worker or PM), a toggle overview, q quit.
t works only within the monitor's own zellij session (decided). Each repo
normally runs in its own zellij session (ZELLIJ_SESSION= in its session
files), and zellij action go-to-tab cannot cross sessions. So each overview
row names its zellij session, and the user switches with zellij's own session
manager. Cross-session switching (a zellij plugin) is out of scope. The exact
keymap and styling are the builder's call within this shape.
Constraints
- macOS with
/bin/bash3.2 is a first-class target. The snapshot must run
there, and must not use bash 4+ features or slow pattern substitutions over
large strings (see task-remove hangs at 100% CPU in _la_strip_line under bash 3.2 with a UTF-8 locale #266). - The repo's norms apply:
tools/check-drift.shfor shared regions,shellcheck -x, bats tests, CHANGELOG, docs (README, the steering templates, the loadout
workflow docs), and the pre-PR checklist in.claude/gh-workflow.md. - The CI matrix gains a Rust job (build plus tests) on macOS and Linux.
TASK_FORCE_HOMEis respected everywhere. Tests never touch the real radio
state.- Repos are discovered from radio session files (the
REPO=field) plus the
current repo. No separate registry.
Delivery: the epic and its sub-issues (proposed order)
- Snapshot command and schema (bash). Covers in-flight, next and done, the
attention signals, the tracker hooks, the done-cache,--all, bats tests,
and the schema documented. - PR
## Summaryconvention. Worker prompts on every loadout, the PR-body
parse with its fallback, and docs. It can land in parallel with 1, since it
is only a prompt change and starts paying off immediately. - PM queue. The
task-monitor next …subcommand, the file format, the
Todo fallback, and PM prompt guidance on every loadout. - ratatui front end. Both screens, the keys, the watcher, the slow poll, the
stale handling, and its Rust tests. - Install and CI.
install.shcargo build and link with a graceful skip,
the CI Rust job, README section, and an upgrading note. - kiro awaiting signal (investigation). Does kiro-cli expose a hook event
that can driveradio awaiting? If so, wire it into the three kiro installers
so❓and attention signal 3 work for kiro workers. If not, document why.
Left to the builder
- Rust crate layout, dependencies beyond ratatui (watcher crate, JSON), and
styling. - Exact JSON schema field names, within the data described above.
- Queue file format and path, under
~/.task-force/. - How
tfinds the tab: the worker sidecarTAB_IDand the PM sessionTAB_ID,
verified by name the waytask-donedoes since task-done closes a stale TAB_ID without checking the tab is its own, killing an unrelated worker after a zellij restart #260. - Debounce and poll intervals, and the done window, within the stated defaults.
Decisions taken in discussion
- Real-time ratatui front end over a bash
snapshot --json. Built with cargo at
install, since both of the user's machines have Rust. - No model tokens spent by the monitor. The PM's only cost is the queue command.
- Per-repo view first, with an all-repos overview. Attention means exactly: an
unread PM inbox, a review waiting on a merge decision, or a worker that is
waiting on the user. - jira/notion show the slug before the PR exists, with no PM-written
descriptions in v1 (see Data sources). tworks only within the monitor's own zellij session.- No
❓for kiro in v1.
Activity
task-monitor 4/6: ratatui front end (repo view + all-repos overview, …