Reconciled 2026-06-19. Status markers: DONE / PARTIAL / open (no marker). CLAUDE.md §7 build phases (0–5) are all complete; everything here is post-phase work.
IF the parent project has a lot of repos, then the herder bar can extend too far when trying to start a chat window. The auto heigh feature should not count unactive repos, or repos without any active agents in them. The auto hieght features should only make room for showing repos with active agents, leeaving the rest of the screen space to the change window
Resolved:
stage_top_rows()counts only agent-bearing project rows (teststage_top_rows_ignores_idle_zero_agent_projects).
When an agent is active in a project, I want it indnetd so I can clearly see that the agent is under that project. If an agent opens sub agents under it's perview, if possible I want those indended in that agent which is indented under the project.
Resolved: agent rows are now indented (
AGENT_INDENT) under their project header in grouped view, and sub-agents nest one level deeper still (their├─/└─tree glyphs were already there), so the roster reads project → agent → sub-agent. Indent is suppressed in flat view (no header to nest under).ui/table.rs(session_row/subagent_row).
I select a row in herdr, I press n to start an agent, it sarts and in herdr is report 15% context used up and processing already? I haven't even asked a question yet
"Processing already" half resolved: instantaneous CPU now seeds idle on a fresh agent's first sample instead of inheriting
ps %cpu's lifetime average (fix:1e54cd7). The "15% context" half is not a bug: that's real consumed context — the first API call already includes the system prompt, tool definitions, CLAUDE.md, and env, which on a 200k model lands ~15% before you type anything (monitor.rs:146tracks the last call's input+cache tokens). Left as-is rather than hiding accurate data. See theherdr-statusskill.
The hightlight row color is too bright and hard to read. The colors are overall could use some work. Build a theme system were we can slot in different colors for the UI. The default color should resemble that of Dracula Dark
Resolved: the Dark theme is now built from the Dracula palette (
DRAC_*consts incore/theme.rs), and the selected row uses a muted Dracula "current line" band (selection_bg/selection_fg) instead of full-brightness reverse-video — the "too bright" complaint. The theme system was already slottable (Dark/Light/None viaThemeMode);Nonekeeps reverse-video for NO_COLOR. Seetheme.rs+ui/table.rs.
Both the Status and Activity readouts in herdr seem to lag behidn what is actually happening with the agent. Often times staying on processing while the task has compelted. Activity often lags behind or does not repreesnt the token consumotion or what is going on.
Status lag resolved: instantaneous CPU (
ps time=deltas, not lifetime%cpu) + theuser/tool_result decay branch, so a finished agent leaves Processing within ~1–2 ticks (fix:1e54cd7,perf:8b191cd). Still open: the Activity sparkline (activity_history) accuracy vs. real token throughput.
I often see values of 6000/h or 4000/h. It seems to me that those should be the red values
Resolved with "$/h is wild" below: those 6000/h readings were the bug, not real rates — a cost delta divided by a hardcoded 2s tick during a sub-second event-loop burst. Now the rate is Δcost ÷ real wall-clock, so magnitudes are sane and the existing
>10 → redthreshold inui/table.rscolors a genuinely hot agent red. Threshold coloring (low/mid/high) was already wired to the theme.
using /clear on an agent window should reflect in herdr that the context has been cleared
Resolved before this session:
fix:e048a41 (attribute transcripts by session id; reset context on /clear) + testmerge_resets_transcript_state_when_session_id_changes.
Can tmux add a border to the claude window just as herdr has it's own window title and border ie -herdr-----
Resolved by composing tmux (no rendering, §0.1): when an agent is staged, herdr sets
pane-border-status top+ a#{pane_title}border-format on its window and titles the agent pane "Claude — " (and its own "herdr"), plus a heavy border line and Dracula colors (pane-active-border-stylepurple = focused,pane-border-stylecomment = idle). The agent's top-edge border is the divider under herdr, so it reads like-herdr-. Options unset on unstage.terminals::set_stage_title/clear_stage_title→tmux::set_stage_border. Note: the left/right/bottom edges stay unbordered — they're the terminal's outer edge, where tmux draws nothing, and herdr can't render a box into Claude's pane without becoming a terminal emulator (§8). A full 4-sided frame isn't reachable within the design.
A value that reflects the cost over multiple (all) sessions. For the repo collection as a whole and individual projects.
Resolved:
history::all_time_summary()rolls the full history CSV into collection-wide totals and a per-project map (ProjectTotal). The fleet strip shows all $X (tokens) for the collection; each project header shows its all-time spend as Σ$X (matched by name; shown even for idle projects with past sessions). Cached onApp.all_time_summary, refreshed on the weekly-summary cadence (~30s). Limitation: per-project match is bydisplay_name, so a custom session name won't fold into the dir.
A script to quickly launch tmux and start herdr tmux attach -t work cargo run --release -- "/mnt/c/Users/Ben Bracamonte/Work"
Resolved:
./quickstart.sh [PARENT_DIR]— rebuilds release (never a stale binary), then creates or reuses a tmux session (defaultwork, overrideHERDR_SESSION) with herdr running over PARENT_DIR (defaults to the repo's parent dir; handles paths with spaces). Switches client if already in tmux.
set -g mouse on Mouse scroll works on roster page but in agent window it scrolls the messages i eneted. Can we make the scroll wheel scroll the responses like a normal agent window
Resolved the tmux-native way:
quickstart.shnow runstmux set -g mouse on, so wheel-scroll over the staged agent pane enters tmux copy-mode and scrolls its responses. herdr deliberately does not call crosstermEnableMouseCapture— that would fight tmux's mouse handling in herdr's pane and lose native text selection (§0.1, tmux owns window management). Addset -g mouse onto~/.tmux.confto make it permanent.
Is this possible without betraying our design doc? I want to be able to click between the herdr window and the chat window
Yes, without betraying the doc: it's tmux's
set -g mouse on(now set byquickstart.sh) — click a pane to focus it. herdr stays out of mouse capture so tmux owns click-to-focus, fully aligned with §0.1. Pairs with "Add Mouse Scrolling" above.
What kind of charts would be informative that we could display along side ond beneath the herdr nav bar?
v1 shipped: the Phase 5 fleet trend strip beneath the roster (
Gtoggles) — status counts, live burn $/hr + a trend sparkline, today/week cost (feat:bb8ec94,ui/fleet.rs). Per-agent values stay in the roster (Context bar + Activity sparkline columns). Optional next: daily-cost BarChart or a dedicated graphs overlay if the strip earns the space.
We need a way to track token usage over multiple sessions
Resolved: the fleet strip now surfaces wk tokens alongside wk cost, plus all-time tokens in the new all-time segment (
ui/fleet.rs,fmt_tokens). Multi-session token totals come fromhistory::weekly_summary.total_tokensandhistory::all_time_summary().total_tokens.
when waiting for an approavl to run a tool, there is no indicator in herdr roster view that it needs attention or can be arrpoved. Sometimes I see a purple Need Input notice, but then it kept going. maybe becaues I was on auto mode?
Partial: Phase 4c added the approval inspector (
A) —tmux capture-paneshows the real permission dialog (invisible in JSONL) with approve/deny/interrupt from the roster (feat:581c86d). NeedsInput detection also tightened via the status work. Persistent indicator now added (ui/table.rs): a bold⚠Nbadge on any project header hosting N agents awaiting approval, plus a leading⚠on the agent's Status cell (visible in NO_COLOR too). Still open: the auto-mode "kept going" interaction.
The nuimber is up and down and all over the palce. I feel like it should be the average of an hour? or er min?
Resolved: burn rate is now Δcost ÷ Δ(real wall-clock), EMA-smoothed (
smooth_burn, α=0.3), sampled no faster than every 2s (MIN_BURN_SAMPLE_MS) — mirroring the CPU instantaneous-rate derivation. The olddelta * 1800.0assumed a fixed 2s tick, but the Phase-2.5 event loop fires at irregular, sub-second intervals, so a JSONL burst spiked $/hr into the thousands and quiet ticks decayed it erratically. Newprev_cost_sample_msfield on the session carries the sample baseline. Tests:smooth_burn_steadies_a_spiky_signal,smooth_burn_decays_toward_zero_when_idle. (app.rsrefresh).
Each agetn should have a indicator about which model they are current running.
Resolved via the existing roster detail (
Enter→ui/detail.rsshows "Model: …", already shortened bymodels::shorten_model). Per user decision, deliberately not added as a roster column to keep the row uncluttered.
When reszing the window smaller they resize properlly. When resize the window larger with both the roster and the agent window open, then the agent window stays the compressed size while herdr gets bigger. Upon getting larger the agent window should take more of the screen space when possible while still maintaiing the min-height for the roster
Resolved:
Event::Resizeonly repainted —resize_stage_topwas never re-issued, so tmux kept the old height and a grown window left the agent pane compressed. The event loop now callsApp::refit_stage()on resize, which re-fits herdr to its roster height (capped to preserve the agent's min) when an agent is staged. Gated on "staged" so it never fights a manualCtrl-bresize. (src/main.rs,app.rs.)
Launhcing an agent in the roster view with n. The repo will sort to the top with the now active agent, leaving the roster selection on an unintended repo. The selection bar should follow the now sorted repo.
Resolved: selection is now sticky by stable identity, not row index.
refresh()captures aRosterSelKey(project path for a header, PID for an agent) before the re-sort and re-anchors the cursor onto it afterward (selection_key/reselect_by_key). So afternthe cursor follows the launched repo to the top — and the roster no longer drifts under you on any status-driven re-sort. Testreselect_by_key_follows_the_same_entity_across_a_resort.
The roster bar should be a minimum for 12 rows when there are a small number of active agents. Ruight now there is one active agent and an open chat window and I see 7 rows. Header, Columns names, 2 repos, session overview and fleet bar.
Resolved:
stage_top_rows()floor raised 6 → 12 rows.terminals::cap_stage_top_rowsstill trims it on short terminals so the staged agent pane keeps its minimum, so the floor never starves the agent.
If the agent is waiting for a repsonse from the API, the roster displays Waiting. The a task is complete the roster also dispalys Waiting. When a task is complete and the agent is waiting for the next task from the usr, then I want that task to be Job Done. So we can tell when we are waiting on the API and when we are waiting ont he user
Resolved: new
SessionStatus::JobDone("Job Done", cyan).infer_statusnow splits the two: assistantend_turn(turn complete, your move) → Job Done; auser/tool_result line where Claude still owes output (request in flight) → Waiting. (monitor.rs; testsassistant_end_turn_reads_as_job_done_not_waiting+ the existing user-branch tests.)
Somtimes we hit an API error. The Roster should display Error in the stats when this is encounted
Resolved: new
SessionStatus::Error(red, sorts just below Needs Input). Claude Code writes API errors as a<synthetic>isApiErrorMessageline ("API Error: 529 Overloaded…");transcript.rsemits aTranscriptEvent::ApiError,monitor.rskeeps it sticky (last_was_api_error) until a newer message supersedes it, andinfer_statusreports Error when CPU is low (an active retry stays Processing). Testsapi_error_flag_reads_as_error_when_idle,active_retry_after_error_reads_as_processing.
The per agent sparklin looks like it only has two states, on and off The fleet sparkline has all sorts of varitaion. Make the roster sparkline more granula like the fleet sparkline
Resolved:
activity_historynow stores a continuous CPU% per tick (Vec<f32>) instead of a discrete status level that only ever produced 1–2 bar heights.format_sparklinescales each bar against a 100% ceiling (a multi-core spike clamps to full, idle ticks blank), giving fleet-strip-like granularity. Testsparkline_scales_cpu_to_block_heights. (session.rs.)
Surfaced by the comparable-tool research (docs/COMPARABLES.md, 2026-06-20). models.rs::shorten_model
collapses any …opus-4-8… (or 4.5/4.7) to "opus", which resolves to the opus-4.6 profile = $15/$75
per M. Real Opus 4.8 is ~$5/$25 per M. Every model that isn't opus/sonnet/haiku also lands on the Opus
fallback profile, over-counting cost. herdr is the binary you run on Opus 4.8 and it mis-costs itself.
Resolved: rewrote
built_in_profileto current Anthropic list pricing (Opus 4.5–4.8 $5/$25, Sonnet 4–4.6 $3/$15, Haiku 4.5 $1/$5; cache read 0.1× / 5m write 1.25× input — verified against theclaude-apireference), matched by model family prefix so every version/date/[1m]suffix resolves. Replacedshorten_model's opus-only special-case with a boundary/version-aware matcher (family-major.minor, split on non-alphanumerics so date stamps and[1m]don't confuse it). The fallback now prices at mid-tier (Sonnet) not Opus, still flaggedcost_estimate_unverified. Testsopus_48_priced_at_current_rate_not_legacy,shorten_model_extracts_family_and_version(models.rs).
Surfaced by the same research. Claude Code writes the same assistant message's usage on multiple
physical JSONL lines during streaming; herdr accumulates every usage line (no dedup), so multi-line
turns inflate token + cost totals. Both ccusage (mod.rs:292) and tokscale (claudecode.rs:498)
document and handle this.
Resolved:
transcript.rsnow capturesmessage.id+ top-levelrequestId;monitor.rskeys each usage line by(message.id:requestId)and max-merges per tier (merge_usage), adding only the increase over any prior emission to the running totals — on both the own-agent and subagent paths. Dedup state persists across incremental ticks (carried bymerge_discovered_session), resets on/clear(fresh session) and on file truncation. Testsmerge_usage_dedups_streaming_duplicates,merge_usage_keyless_lines_add_in_full(monitor.rs). Deferred (lower value once the table above is correct):CostMode::Auto(trustcostUSD— present only sporadically in transcripts, and our computed cost now matches it); the 5m/1h cache-creation split (real but ~1.6× on a minor tier); a>200klong-context tier (N/A — current Opus/Sonnet 1M context is flat-priced, no premium). User-configurable price overrides need a config-file subsystem herdr doesn't have yet (models::set_overridesis wired but unused).
herdr infers the invisible permission-prompt NeedsInput state (<2% CPU + stale tool_use). Claude
Code's Notification hook fires exactly when an agent needs permission/input — make it authoritative.
(COMPARABLES §7 item 5; whiteroomed from claude-tui's hooks-as-push-signal.)
Resolved (Phase B): opt-in, merge-safe.
herdr hook installmergesNotification/Stophooks into~/.claude/settings.json(idempotent; preserves the user's other hooks;herdr hook uninstallreverts). The hook runsherdr hook notify, which reads the payload on stdin and atomically writes~/.claude/herdr/<session_id>.json(Notification→NeedsInput,Stop→JobDone). herdr'snotifywatcher now also watches~/.claude/herdr/, andmonitor::apply_hook_overridetreats a fresh status file (newer than the transcript mtime, <5min old) as authoritative — self-clearing once the agent writes new JSONL, so no one has to delete the file. Works without hooks installed (pure no-op). Newcore/src/hookstate.rs+src/hookcmd.rs; testsstate_and_settings_under_temp_home,read_missing_is_none; verified end-to-end against a temp HOME. Usage: runherdr hook installonce, then restart Claude Code sessions. Deferred: agent-deck's "acknowledged-downgrades-waiting" (stop nagging once you've attended an agent) — the override already self-clears on the agent's next JSONL line, so this is a minor polish; and stale-file cleanup for dead sessions.
COMPARABLES §7 items 6–7 (tmux -C control-mode refresh + low-latency send-keys; orphan-pane
recovery) were borrowed from agent-deck. On investigation against herdr's actual architecture, both
are obviated — herdr is not agent-deck:
- Orphan recovery — already covered.
~/.claude/sessions/<pid>.jsonis written by Claude Code itself (v2.1.183:sessionId/cwd/startedAt/status/name/…), so herdr'sdiscovery::scan_sessionsalready finds every running agent — herdr-launched or manual — and maps each to its tmux pane via TTY (ps→pane_for_tty). agent-deck needs orphan recovery only because it owns namedagentdeck_*tmux sessions; herdr owns none (§0.1/§8) and discovers via a restart-independent registry. No gap. - tmux
-Ccontrol mode — doesn't earn its complexity. herdr's refresh signal is JSONL writes, caught reliably by thenotifywatcher (transcripts live on the Linux fs~/.claude/projects, not the flaky/mnt/cmount), and herdr sends whole prompts, not keystrokes — so neither the%outputrefresh nor the persistent-send-keysbenefit applies. A persistenttmux -Csubprocess- protocol parsing would add real complexity (and edge toward §8) for ~zero gain. Skipped per the plan's "commit only if it earns its complexity" gate.
Shipped the one real adjacent win the investigation surfaced: herdr parsed past Claude Code's session
name(it read only pid/sessionId/cwd/startedAt).discovery::scan_sessionsnow liftsnameinto a newClaudeSession.cc_name, shown read-only in the detail view under Project (e.g. "audit-model-pricing-bugs") so multiple agents in one repo are tellable apart. Kept separate fromdisplay_name()/session_nameso it never disturbs history's per-project cost folding. Follow-up (UX decision, not done): promotecc_nameto the roster agent-row label and the staged- pane border title.
COMPARABLES §7 items 8–10. Applied the plan's own gate ("each graph reflects real data and is something we actually glance at; defer any that don't earn the space").
Shipped D1 — daily activity heatmap on the fleet strip (
G). Newhistory::daily_cost_series
history::intensities(the 5-level GitHub-contributions bucketer vendored from tokscale'scalculate_intensities: ≥0.75→4 … >0→1, else 0) →history::daily_activity(ACTIVITY_DAYS=14). Cached onApp.daily_activity, refreshed on theweekly_summarycadence (~30s) so it never readshistory.csvon the render path.ui/fleet.rsrenders one cell per UTC day (· ░ ▒ ▓ █, shaded by that day's spend vs. the busiest day) — the "days" time axis the live burn sparkline (~30s) can't show. Testsintensities_bucket_by_ratio_to_busiest_day,daily_cost_series_length_matches_window,heatmap_maps_levels_to_shades. (Populates as sessions complete — samehistory.csvthe all-time/ weekly totals already use.) Deferred with rationale:
- D2 (P90 personalized limit + 5h-block projection) — would introduce a 5-hour "block" entity in tension with the project-first model (CLAUDE.md §2); the plan-limit tables are community guesses; and the JSONL limit-notice regexes can't be verified without a real "limit reached" sample. Heavy + speculative for a narrow quota-prediction payoff.
- D3 (turns-until-compaction / wasted-context) — value dropped sharply with 1M-context models (auto-compaction is now rare), and the detail view already shows context %/bar. Marginal vs. effort. Follow-up if wanted: a full multi-row GitHub-style heatmap overlay (the
intensitiesbucketer is already in place and tested, so it's mostly render plumbing).
COMPARABLES §7 tail.
Shipped:
- E1 — Error badge on project headers. The header already flags blocked agents (
⚠N); now it also flags errored ones with✕N(red,t.status_error), shown only when a project hosts an agent inSessionStatus::Error— same tasteful, only-when-relevant pattern. (Chose this over the full per-status count breakdown agent-deck shows on collapsed groups: herdr always shows the agent rows, so a full breakdown is redundant clutter — and the user keeps the roster lean.)ui/table.rs.- E2 — status filter quick-keys.
!=needs-input@=processing#=waiting$=idle jump straight to a filter (vs.fwhich cycles); pressing the same key again toggles back to All for a clean on/off (App::set_status_filter). Documented in the?help overlay.app.rs. Teststatus_quick_key_sets_then_toggles_back_to_all. Deferred: E3 (freshness[Nm ago]) — herdr's roster is event-driven (notify watcher + 2s safety tick), so it's near-always fresh; a staleness indicator (valuable for vscode-claude-status's network panel) would read "now" almost always here. Low value.
what is the difference between idle and waitign anyway? And jobs done? I feel like I am conflating terms
Resolved:
fleet_counts(app.rs) lumped Job Done into thewaitbucket alongside WaitingInput; it now counts as idle — a finished-turn agent isn't doing anything, it's your move. Sowaitis only a genuine in-flight API request (WaitingInput). The taxonomy: Processing = working · Waiting = blocked on the API (request in flight) · Job Done = turn complete, your move · Idle = no recent activity (stale Job Done / parked tool call) · Needs Input = approval · Error = API error. Added this glossary + the!!/!F/REC/⚠N/✕Nindicator legend to the?help overlay (ui/help.rs) so the terms are discoverable. Testfleet_counts_job_done_reads_as_idle_not_waiting.
I think we have the functionlailty but I don't see any notification that an agent is asking for approve deny or some other reponse from the user before proceeding. If its just approve deny then I would like to be able to anserw that from the roster view. Other questions the user needs to go to the claude window for more details
The agent names, which seem to be just the same as the project that they were stated on are just regualr gray. Can we use a more poppy dracula themeed color? I want those to stand out more
I thgouth that we had a hook to tell when an agent does stuff so we could more relaiably determine when an agent needs direction or is done with the job etc. How do we install that? target/release/herd install ?
Install it with
herdr hook install(then restart Claude Code sessions); it merges opt-inNotification/Stophooks into~/.claude/settings.jsonandherdr hook statusshows the live state. Fixed a blocker found here:hook_commandwrote the binary path unquoted, so Claude Code's/bin/sh -cword-split a path with spaces (/mnt/c/Users/Ben Bracamonte/…→/bin/sh: /mnt/c/Users/Ben: not found) and the hook silently failed. Now shell-single-quoted (shell_single_quote); the barehook notifysuffix lets re-install replace the old broken entry. Re-runherdr hook installonce to repair an existing install. Testsquotes_paths_with_spaces,escapes_embedded_single_quotes,command_keeps_bare_hook_notify_suffix. Also wired groundwork for the (deferred) session-limit notice:transcript::maybe_capture_limit_noticelogs a real "usage limit" line underHERDR_LOGso a verified regex/badge can follow.
When an agent spawns sub-agents (e.g. Explore agents in a code review), I want them tabbed under the orchestrating agent with their own cost/context, their token spend rolled into the parent's counters (kept even after they finish and disappear), and reflected in the today/wk/all fleet counters — which were also constantly 0.
Root cause: the sub-agent structs (
SubagentRollup/subagent_breakdown/subagent_row) were all built and correct, but fed by a dead discovery path.discovery::scan_subagentsscanned/tmp/claude-{uid}/{slug}/{sessionId}/tasks/*.jsonl("Feature #29", inherited from claudectl) — but Claude Code v2.1.x stores only.outputscratch there. Real sub-agent transcripts live at~/.claude/projects/{slug}/{sessionId}/subagents/**/agent-*.jsonl(+agent-*.meta.json). Soactive_subagent_countwas always 0; no rows, no token attribution. Measured on live data: 409M tokens across 491 sub-agent transcripts (2.45M of it output) were being silently dropped. Fixes:
scan_subagentsnow derives the dir fromjsonl_path(resume-safe) and recursively collectsagent-*.jsonl(skips.meta.json), splitting all-discovered (subagent_jsonl_paths, drives rollup so a sub-agent that came and went is still counted) from the fresh-by-mtime active subset (active_subagent_jsonl_paths, <SUBAGENT_ACTIVE_SECS=25s → individual live rows; older collapse to "completed (N)").discovery.rs.monitor::refresh_subagent_rollupsiterates the full set;update_subagent_rollupresolves a human label once from theagent-*.meta.jsonsidecar (agentType · description, e.g. "Explore · Map launch→discovery"). Rollup tokens/cost already fold intosession.cost_usd/totals viafinalize_usage, so the parent agent row + fleet live-cost already include sub-agents.- Fleet today/wk/all were CSV-only (
history.csv, written only atFinished) → 0 for never-finished agents. NewApp::fleet_totals()folds live (active-session) cost+tokens into today/wk/all (skipsFinishedto avoid double-count); wired intoui/fleet.rsstrip +ui/table.rstitle. Tests:scan_subagents_finds_agent_jsonls_and_splits_active_by_mtime,subagents_dir_derives_from_resolved_transcript_path,scan_subagents_clears_when_no_subagents_dir,resolve_subagent_label_reads_meta_sidecar,truncate_label_*,fleet_totals_fold_live_spend_and_skip_finished. Deferred: per-sub-agent context % (the rollup accumulates deltas, not last-call absolute context, so the sub-agent rows show cost/tokens but-for context); a cap on simultaneous active rows if a wide fan-out ever floods the roster (YAGNI).
Those totals should be stored somewhere locally and loaded in when the app starts. I would like to keep that tally running
Already implemented in
77437e5("Bug fixes", 2026-06-21): a session-keyed ledger at~/.local/share/claudectl/ledger.json(core/history.rs,struct Ledger). It's loaded on startup (App::with_parent→Ledger::load, seeded once from the legacyhistory.csvon first run), upserted from every live session each tick, and atomically saved (tmp+rename) every ~30s and on quit (save_tally, wired atmain.rs:193). today/wk/all-time and the 14-day heatmap all derive from it (weekly_summary/all_time_summary/daily_activity), so the running tally survives restarts. If you still saw it reset, you were on a stale binary — rebuild via./quickstart.shorcargo run --release. Verified by the workspace test suite (245 green).
Once a repo has an active agent in it, don't auto store the roster by statss or whatever. Sort it alphabetically by repo name once there is an active agent in one and it has been brought to the top of the roster. Don't auto sorts those at the top over and over, just alphebecially
Already implemented in
77437e5:App::project_groups(app.rs:4189) sorts agent-bearing repos to the top, then stable alphabetical by name — and deliberately not by status/urgency/cost, so a repo's position no longer re-floats as its agent churns NeedsInput→Processing→Idle. Ordering only shifts when a repo gains or loses its first/last agent. (Default view is grouped; sticky selection viaRosterSelKeykeeps the cursor on the same repo across any re-sort.) Same stale-binary caveat as above if you still saw rows jumping.
Needs Input somteimes shows up in the status. I would think that we have a better way of telling for sure if an agent needs input or not. Right now it seems to infer it incorrectly
Resolved: the flicker was the
tool_usebranch ininfer_status(monitor.rs) flipping to NeedsInput the instant a tool was outstanding with low CPU — which is indistinguishable from an auto-approved tool still executing (the Claude process is idle while a Bash/web call runs), so it flashed NeedsInput then snapped back to Processing when the ToolResult landed. Now an outstanding tool must outlive a normal round-trip (NEEDS_INPUT_STALE_SECS = 10) before the heuristic calls it NeedsInput; within that window it stays Processing. To keep accuracy when the inbound hook is installed,apply_hook_overridenow lets a freshNotification(NeedsInput) override that low-CPU Processing too (deferring only to a visibly-busy CPU>5 agent or an active Error), so a real permission prompt still surfaces instantly. Testsfresh_pending_tool_reads_as_processing_not_needs_input,aged_pending_tool_reads_as_needs_input,busy_pending_tool_reads_as_processing. The authoritative path remainsherdr hook install(status from fact, not guess).