Skip to content

feat: agent visibility + never lose a pane's answer - #96

Merged
hasuwini77 merged 17 commits into
mainfrom
feat/agent-visibility
Oct 8, 2026
Merged

hasuwini77 merged 17 commits into
mainfrom
feat/agent-visibility

Conversation

@hasuwini77

Copy link
Copy Markdown
Owner

Closes #95

Added

  • Live agent rows in the transcript (role, model · effort, tool, elapsed, tokens)
  • Pinned status line under the prompt (lead only; $ = measured session cost only)
  • Nested dispatch tree from $.agent.list(); waiting/failed states; agent-in-view highlight; rebuilds after reload
  • /agt flag + value autocomplete; pane tools load eagerly

Fixed

  • Reaper never closes an unharvested pane; respawned roles start unharvested; stale answer files ignored
  • Worker saves its answer without a wire identity; lead self-wakes on a silent done
  • spawn_pane refuses when the session runs an older agentille (orphaned versions ignored)

Verification

  • claude plugin test .: 306 pass / 0 fail (209 at v3.6.1)
  • scripts/validate.sh clean; plugin validate --strict exit 0
  • 5 code reviews; all P0/P1 fixed and confirmed (final: PASS)
  • Live in a real session with this build: band tree, transcript row, lead status line, autocomplete (flags, values, during a running turn) observed

🤖 Generated with Claude Code

hasuwini77 and others added 17 commits October 8, 2026 13:48
The band only knew agents it saw spawn, so a module reload emptied it and
nested agents showed as one flat list. It now reads $.agent.list() at session
start and while agents work: unknown running agents are adopted (model, effort
and tokens left blank, never guessed), parent ids nest the rows, and the list
status draws waiting, idle, failed and killed. The agent in view is drawn
inverse, and a sub with no known model gets a ? chip instead of "pane".

Also adds the ctx object and three no-op modules (rows, status, autocomplete)
that the following steps fill in.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
One pinned line (run, working subagents and panes, done, tokens, session
cost, context) so the run's state survives the band scrolling away. The
dollar figure is only the measured session cost, never a per-agent share;
the line clears once nothing works and every finished agent is past the
done grace, with a clock timer for the quiet case.

Cost and context come from a session.measure hook matched on `context`:
register.js already holds the unmatched one and a second unmatched hook
is refused.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
An Agent tool call now draws as the agent it started: role, model·effort,
current tool, elapsed time, tokens and the call's description, from the
live map. Unknown agents, errored and interrupted calls keep the engine's
row. A running call has no output yet, so an agent.spawn observer maps its
tool_use_id to the agent id (bounded at 200).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
An adopted agent the list marks idle started working and never ended, so
the ticker and the list poll ran forever; a spawned agent the list once
named and no longer does was never finished. Adopted rows no longer show
a misleading 0 token count. The list read is throttled on the engine
clock (testable), retried rarely after a failed read, and only repaints
when something changed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The /agt flags were typed blind; prompt.autocomplete now offers them. The
pane tools set isDeferred:false so panes mode never needs a ToolSearch hop.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
It advances a 90 s grace on the mock clock and hit the 5 s default under load, failing the pre-push gate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A done or idle agt- pane closed after its grace period even when nothing
had been read from it, so a worker that could not report (a lead on an
older mod, no wire) lost its whole output. The reaper now closes a pane
only once its answer file exists, its wire done message arrived or the
lead closed it; otherwise the pane stays open and is flagged once.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A lead on an older mod gets no wire message from its workers and waited
blind. When an agt- pane of its run goes done or blocked and no wire done
message arrives within 20 s, the lead's mod now sends the lead session
one message naming the pane and how to read it (herdr pane read, or tmux
capture-pane). Once per pane.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A session keeps the mod it started with, so a plugin update left a lead
without the wire and its workers unable to report. The mod reads its own
version at session start and compares it with the sibling version
directories of the plugin cache (no network); if a newer one is
installed it toasts once and spawn_pane refuses until Claude Code is
restarted. The check repeats at each spawn so a mid-session update is
caught too. The recon line now takes its version from the running
skill's own plugin.json.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
--mode p now offers panes, --formation g offers gauntlet. The hook
matched only dash-led tokens, so a value word never reached it; it now
matches any typed /agt draft and decides from the previous word.
argument-hint lists --formation.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The pinned status line was refreshed from an AbovePrompt render hook that
ran in every pane and re-read the whole run on each redraw.

- Only the lead pins a line: session.measure and the new tick callback
  return early when ctx.isLead is false, so a worker pane's ticker never
  reaches ui.status.
- The refresh left the render path. The redraw ticker in register.js calls
  the callbacks on ctx.onTick with the `now` it already read, also on the
  tick that stops it. They get an `io` ({ status, after, now }), not `$`:
  the hook checker follows `$` only into top-level functions of the same
  file and refuses it into a callback or an import. session.measure stays,
  and no new unmatched hook is added.
- The pin is tried and remembered only once the host took it. `shown`
  starts as a sentinel, so the first refresh after a reload writes even
  when it clears a line the old module left pinned.
- Rows: a finished agent's row is cached per agent, keyed on state, tokens,
  list status and end (applyStatus moves the list status after an agent is
  done), rebuilt while it waves goodbye, capped like the call map. A
  workflow agent's tool_use_id, shared by its whole run, is no longer
  mapped to one agent.

Every change has a test that fails without it. pin is exported and tested
with a stub io, since the test kit swallows a throwing ui.status hook. The
grace test now waits one tick (advance(300)) instead of mounting the band,
which no longer pins the line. rows.test.ts already checked an interrupted
call of a working agent; the new test adds a finished agent and a running
one found through its spawn.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A role can be respawned (a fix, a retry) under the same pane name, but
harvest state is keyed by name and nothing ever cleared it: the new pane
inherited the earlier pane's harvested mark, wire report and wake, and
the earlier pane's answer file counted as its own, so the reaper closed
a pane nobody had read.

spawn_pane now clears the name from the four sets. An answer file counts
only if it was written after the mod first listed that pane, the rule the
tmux done-file already follows; herdr panes are tracked the same way.
A restarted lead therefore flags a leftover pane whose answer predates
it instead of reaping it.

A stranded idle pane is flagged "idle, not harvested", not "done". The
unused reapable() is gone; its tests call reapPlan().

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The answer file agents/pane-<role>.md was written only by a worker that
had AGENTILLE_NAME, which a lead from before the wire, or a hand-made
pane, never sets. Such a worker published nothing, so a lead that reaps
a done pane left its only copy of the output behind.

When the worker has no wire name but its multiplexer names the pane
agt-<run>-<role>, it now saves the answer at the end of each main-loop
turn under that name and sends nothing: there is no lead to message.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude Code leaves a replaced plugin version in the cache with an
.orphaned_at marker. After a rollback, a newer orphaned folder sat beside
the version that really is installed, so spawn_pane refused every call
and a restart did not help. The check now looks for the marker, once per
folder newer than the running version, and ignores orphaned ones.

The self-wake fires only for a finished pane. A blocked worker waits on
the person; waking the lead for it cost a full turn and used up the
pane's only wake, so the lead was never told when it later finished
without reporting. The wake names `herdr agent read <name> --source
recent-unwrapped`, the unwrapped read the harvest loop already documents.

The /agt value completion finds the flag before the cursor by scanning
back from it instead of splitting the whole draft on every keystroke,
and leaves the "is this a /agt draft" test to the hook's matcher.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@hasuwini77
hasuwini77 merged commit 62af7de into main Oct 8, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Agent visibility + never lose a pane's answer (v3.7.0)

1 participant