tmt-ops · diff
git:20261013.3c64699 to git:20261014.8bf3efe
218 added, 495 removed. Audit A to A.
---
name: tmt-ops
description: Lead a TMT squad - read the squad board, keep member state current after every dispatch and reply, and agree conventions with the user instead of guessing.
---
# TMT squad (for leads)
Use this skill when you lead a squad: `tmt ops squad ls` shows you as the
squad's `lead`. A squad is a TMT room named `squad-<name>`. Each member's board
fields are that member's identity metadata `squad.<name>.<field>`. The authoritative roster and board fields stay in TMT; optional age observations
live in a disposable Ops cache. `tmt ops squad` has the alias
`tmt ops sq`. Bare `tmt ops` prints Ops help. Bare `tmt ops squad` prints the member list followed by
`tmt ops ui opens the board`; `--json` prints only the same document as
`tmt ops sq ls --json`. Run `tmt ops ui` to open the interactive board.
## Read the board
```sh
tmt ops squad ls --json [--squad <name>]
```
`tmt ops sq ls --tab <name>` returns one configured or built-in board tab's document,
- with the same rows, sections, attention, columns and lines as the board. Hidden
+ with the independent CLI rows, sections, attention, columns and lines. Hidden
squads remain included. `leads` lists each squad lead; `all` lists squad summaries.
`--tab` cannot be combined with `--squad` or `--refresh-fields`. An unknown tab
returns `SQUAD_TAB_NOT_FOUND`; an empty tab has an empty rows array.
Define a cross-squad member view in `ops.toml`:
```toml
[tabs]
order = ["tab:needs-me", "leads", "all"]
pin = ["tab:needs-me"]
[tabs.needs-me]
filter = "pending or waiting_on_you"
sort = ["squad", "name"]
[tabs.needs-me.bind]
enter = "jump"
[[tabs.needs-me.section]]
title = "Blocked"
filter = "state = blocked"
sort = ["squad", "name"]
[tabs.needs-me.section.bind]
o = "tab"
```
Run `tmt ops sq ls --tab needs-me --json` for that view. It includes leads and
members across all squads, including hidden squads. Names use the squad-name
rules; `leads`, `all`, `colors`, `order`, `pin` and `hide` are reserved.
`tab:<name>` distinguishes a user view from a squad in `order`, `pin` and
- `hide`; unplaced views follow the default tabs in definition order. Hidden views
- remain reachable through the switcher and `ls --tab`.
+ `hide`; unplaced views follow the default tabs in definition order. These independent views remain reachable through `ls --tab`; they do not add
+ interactive shell tabs.
Filters use the section language: field presence, `=`, `!=`, `and`, `or`, `not`
and parentheses. Fields include `squad`, `name`/`member`, `state`, `pending`,
`presence`, `lifetime`, `role`, `task`, `activity`, projected custom fields, and
`waiting_on_you` (present when a member waits on the recorded user). With no
recorded user, that field is absent. Sort keys ascend by default; prefix `-` to
descend. Missing values stay last; bound numeric values retain numeric order.
The view filter selects rows before sections. Each matching section may repeat
a row, with unmatched rows following untitled; an omitted filter selects all.
Tab bindings override global bindings, and section bindings override tab bindings.
Views use the squad/member/state/task row preset; per-tab `rows` are unsupported.
At most 16 user views and 16 sections per view are allowed. Invalid definitions
fail Config reading with their setting path, including hidden views.
Failed roster or inbox reads set `partial: true` and include `failures` with
- source, optional squad, and error code/message. The board marks a partial view;
- text lists show warnings. Available rows remain visible, so an empty partial
+ source, optional squad, and error code/message. Text lists show warnings; the workspace retains its own partial-read evidence. Available rows remain visible, so an empty partial
view must not be treated as evidence that nobody waits on you.
With `--squad <name>` the document is that squad's; without it, it is always
`{squads: [...], you}`, one document per squad in name order (even for one
squad or none), so read `.squads[]` unless you pass `--squad`. `columns` and
- `lines` are the board's row grid: each column's field, title and sizing, and
+ `lines` describe the independent CLI row projection: each column's field, title and sizing, and
the fields each line of a row shows (`{field, span}`, field null for an empty
- cell).
+ cell). These values remain accepted and validated; they do not compose the
+ workspace's tree, conversation or pending-continuation geometry.
- A cell has optional `token`, its semantic theme role, which overrides
threshold/provider colors for that cell. Team publishes
`lines[1][2].token = "waiting"` for its pending second line; unstyled cells
omit the key. This is decoration metadata, not a changed row value.
- A column's `width` is null, a cell count or a percentage string such as
`"30%"`. Covered-track percentage widths total at most 100% and resolve against data width
- after borders/row marks on the board (gaps are additional); lists resolve after
- gaps. `min`/`max` remain cells.
+ after the CLI grid's gaps. `min`/`max` remain cells; this geometry does not
+ arrange the workspace.
- A column has optional `valueOnly: true` when no row line covers its positional
- track. It remains a value source but reserves no board width; other columns
+ track. It remains a value source but reserves no CLI grid width; other columns
omit this key. See Columns and row lines below.
- A column has `overflow` only when configured: `"ellipsis"` or `"wrap"`.
Without it, cells use ellipsis. Wrapped continuations align to the cell start.
- A wrapped column has `max_lines`, its bounded visual-line count (1–8, default 2).
The last line uses an end ellipsis if cut, even with `truncate = "middle"`. This differs from document-level `lines`,
which describes the configured row grid.
- Text `ls` keeps legacy natural sizing unless a shown column opts into percent
width or overflow. Opt-in text uses the shared grid and fit rules; a pipe's
budget is natural data widths plus gaps before priority hiding, so text may
wrap, truncate or hide columns. JSON row values stay full.
- `squad`: `name`, `roomId`, `layout` (`crew`, `pr-queue`, `minimal` or `team`),
`lead` (a row, or null) and `attention`: `state` (`waiting`, `blocked` or
`normal`), `waiting` (members that owe the user a decision or wait for an
- answer) and `blocked` (members in the `blocked` state). Tabs and the switcher
- reserve a two-cell leading slot: `◆ ` for waiting on you, else `✗ ` for
- blocked, else two spaces. The count follows the name (`◆ product 2`);
- both states append the blocked count (`◆ product 2 ✗1`). The marks carry
- the meaning, including without color.
+ answer) and `blocked` (members in the `blocked` state). These remain CLI/source
+ facts; Overview owns the UI aggregate, while the squad tree displays observed
+ membership state. Agents has no duplicate aggregate count in its tab/header.
- `sections`: always a list. Unless the user defined sections, it holds exactly
one section with `title: null` containing every member except the lead. With
user sections, members that match none follow in a final `title: null`
- section. The lead is shown first by the board and text `ls` only (below);
- the JSON never moves it into `sections`, and member counts exclude it.
+ section. Text `ls` shows the lead first; JSON never moves it into `sections`,
+ and its source member counts exclude it. The workspace's UUID roster includes
+ the lead once in the squad count rather than adding a second synthetic row.
- `squad.noteAnnotations` is optional: open notebook-line requests from the
recorded user to the current lead, as `{requestId, line, quote}` with a
zero-based source `line` and bounded sanitized `quote`. It is absent when
- none are observed; the shared bounded history also governs board markers.
+ none are observed. This remains an independent source projection; the workspace
+ uses the selected agent's response annotation flow rather than notebook markers.
- Each row has `id`, `name`, `lifetime`, `presence` (`active`, `offline` or
`unknown`), `pane`, `activity` (self-reported status, or null), `state`,
`pending`, `fields` (the `squad.<name>.*` values except the internal
leadership marker and retired row note, by field name,
with the user's column sources, formats and field providers applied;
these strings are already display text and must not be formatted again),
`failed` (fields whose provider failed; they show `?`), `annotation` (the user's open note
about this row, or null) and `waitingOnYou` (open requests from this member to
the user), and `staleness` (observed task/state age).
- Every row has a separate `staleness` object, and `squad.notesStaleness`
describes the lead's notebook: `state` (`disabled`, `unknown`, `fresh`,
`stale`), `unchangedSinceMs`, `ageMs`, `activityAfterUpdate` and `reasons`.
Unknown timestamps are null. Age is observed raw task/state or exact notes
content age, not file/core modification time. First observation starts the
clock; never infer older age from a cursor or missing evidence. Text `ls`
labels stale rows and notes with their age. See the reminder configuration
below for reset and evidence limits.
- A row with `pending` owes the user a decision. It is marked ◆, and the crew
and team layouts list it first.
- A row has the optional `digest` object when Core supports the digest policy read:
`{active, digestUntilMs, remainingMs, heldCount}`. Times are milliseconds; inactive
windows have zero remaining time. Failed/older Core reads omit it. The board shows
`digest 30m · 2 held`, rounding remaining minutes up (then `1h20m`); zero held is
- omitted. Narrow rows keep `digest`; expand with `e` for remaining time and held count.
+ omitted. Complete chip units fit the current tree/conversation budget; trailing
+ labels are omitted when space is short. There is no inline `e` expansion.
- Extensions can label member rows. The board asks each extension in `[labels] sources`
in `ops.toml` (default `["digest"]`; `[]` turns it off) for `tmt <name> status --json`,
in the background every few seconds, and shows the labels it supplies after the
member's name, led by the extension's name in `muted` and in the roles the extension
chose, such as `digest Auto · 3 held · Due now`. A member with supplied labels shows
them instead of the `digest …` chip, never both. Nothing waits for it: an absent,
slow, failing or malformed extension changes nothing, and labels from one that stops
answering disappear after 30 seconds. Labels give way to the member's name and the
whole state word: narrow rows drop trailing labels first, show the extension's name
- only with its first label, and show no chip when those do not fit. `e` lists all
- labels under the extension's name.
+ only with its first label, and show no chip when those do not fit. Read the
+ extension's `status --json` for its complete current labels.
The sources are read when the board starts; restart it after changing them.
- A label may offer a choice; its chip ends with `▾`, such as `digest Auto ▾`. Click the
- chip, press `i` (`digest`, rebindable) on a member row, or choose `digest` in its Enter
- menu, to open that member's choices (for the digest extension: Auto, 1 minute up to
+ chip, press `D` (`digest`, rebindable) on a member row, or choose Digest in its
+ action menu, to open that member's choices (for the digest extension: Auto, 1 minute up to
1 hour, and `Custom…`), the current one marked `(current)`. Enter or a click runs a choice; the wheel and Up/Down move the
highlight; Esc closes. The click and the wheel work in every action menu; a click
outside its entries does nothing. `Custom…` asks for a duration such as `20s`, `10m` or `1h`;
empty text goes back to the default. Only the recorded user and the squad's lead may
change a digest, as with `tmt ops digest`; anyone else is told so and nothing runs,
and the check is made again when the choice is applied. A member without an offered
- choice, or no extension at all, says so on `i` and has no `digest` menu entry. The
+ choice, or no extension at all, says so on `D` and has no Digest menu entry. The
extension, not the board, decides what is offered and what runs: the board starts
`tmt` with the extension's own arguments, the chosen text as one argument, never
through a shell, and shows the extension's refusal as is. The chip updates at once
after a change.
- A row has the optional `colors` key only when a cell has a color:
`{field: theme token}`. `colors.state` holds the resolved state token; other
keys come from the user's column thresholds or a field provider's suggestion.
Colors only decorate; read the values.
- States come from the layout: crew and team use `working idle blocked review testing
hold`; pr-queue uses `preparing ready sent merged`; minimal has no fixed list.
Color and order resolve through exact `[squad.<name>.states]` entries (including
layout presets), then the first matching `[[squad.<name>.state_patterns]]`,
then the default. Patterns require `match` and `color`; optional `sort` is
0-999 and `ignore_case` defaults to false. `*` matches any run, `?` one Unicode
scalar, and other characters are literal. Case-insensitive matching compares
each scalar's Unicode lowercase form. Limits: 64 patterns per squad and 256
UTF-8 bytes per nonempty match. An exact entry wins entirely; unspecified
pattern sort ranks after ranked states. State text and tab attention stay the
same. Do not change the user's vocabulary without asking.
`presence` is observed by TMT, not reported by the member. `activity` is what
- the member reported about itself. `tmt ops ls` marks a row by presence (`●` active,
- `○` offline); the board's mark column shows the state the member reported, so a
- member row there never draws `○`.
+ the member reported about itself. CLI row marks and state vocabulary remain
+ independent source facts. The workspace labels observed presence as `active` or
+ `offline`; missing presence remains unknown.
A request tagged `[<squad> · <member>]` from the user is an annotation: a note
- about that row for you to act on. Answer it with `tmt reply` as usual; the
- user's board shows it as ✎ until you do. Never edit the user's notes for it.
+ about that member for you to act on. Answer it with `tmt reply` as usual. Never
+ edit the user's notes for it.
## Reloading open boards
- An open board reloads itself in place, keeping its pid, tab, selection, scroll,
- expanded details, folds, search and picked tabs, when the `tmt-ops` it was started
+ An open board reloads itself in place, keeping its pid, shell section, exact selection, scroll,
+ expanded room UUIDs, pool expansion and search, when the `tmt-ops` it was started
from is replaced (for example by `tmt extension upgrade ops` or `tmt upgrade`) or
when `tmt ops ui --reload-all` asks every open board to. The board waits until it
has been idle for a second and nothing is open: a composer, search, overlay, reader,
pending request or loading view holds the reload, and the footer adds a dim
`reload waiting`. After the reload the footer says `Board reloaded · tmt-ops X`.
Boards started before this behavior shipped need one manual restart.
## Board appearance
- Member and HOME lead groups, outlined panes and inline input/read bands use horizontal
- rules with blank side margins. Pane titles and focus cues retain their reserved
- positions. Ask-lead, settings, pickers and cron overlays keep square frames.
-
- View, Theme, action menus, the tab switcher and the cron list show `›` on the
- selected choice's first line when that line fits. In View, `●` still marks the saved view;
- in the switcher, `[x]` still marks picked tabs. Cursor movement changes neither
- mark nor a cron job's state. Config keeps its selected continuation cues.
-
- On a squad tab the lead is the first row, in the same columns as the members, with a
- dim `lead` after its name (cut first when the name cell is narrow), followed by
- the dim rule `── members · N ───` (`── members · 0 · none yet ──` for a squad
- with no member; the numbers exclude the lead). The rule is not a row: ↑/↓ step from
- the lead straight to the first member. Opening a squad tab puts the cursor on
- the lead, so Enter, `a` and the other row keys reach it. Search matches
- the lead like any row. A squad without a lead keeps its member rows as they
- are. Text `ls` lists the lead first in its own `LEAD` section before `MEMBERS`;
- `--json` is unchanged.
-
- The home tab is `▚ tmt`, with ◆ waiting and ✗ blocked counts after its name. Its command/config name remains `all`. With neither `tabs.order`
- nor `tabs.pin` configured, home is pinned first, followed by leads and squads.
- An explicit order or pin, including an empty array, keeps the existing ordering
- policy; hide always applies. Opening the board writes no configuration.
-
- Only adjacent squad tabs sharing a prefix before the first `-` are grouped,
- with at least two visible tabs (`tmt · core squad`). Built-ins and user tabs
- interrupt groups. Grouping changes display only: each squad keeps its own mark
- slot, count, selection, click and drag target. The prefix is not clickable.
- The `s` switcher retains full names, including hidden tabs.
-
- `tmt ops ui --tabs product,infra,needs-me` picks the tabs this board shows,
- using names from the tab line. Squad names take precedence over user-tab labels;
- use `@tab:NAME` for an unambiguous user tab. `leads` selects leads; omit `--tabs`
- or use `--tabs all` for the default set. Unknown names produce a usage error
- listing valid names. `--squad NAME` must be among the picks when both flags are
- given. This resolver is board-only; `ls --tab` keeps its own names.
+ The board opens one `OPS / UI` shell: `1 Overview`, `2 Agents`, `3 Digest`.
+ Overview contains global attention, usage and provider limits. Agents contains the
+ squad workspace and the selected agent's conversation. Digest keeps the same
+ identity context and existing Core-owned policy controls. A squad's theme applies
+ to Agents/Digest; Overview uses the shared board theme.
- In the switcher, Space picks or unpicks the highlighted tab. `[x]` marks
- picked tabs and `[ ]` unpicked tabs. Enter opens a tab and picks it on this
- board; opening a squad from home does the same. Unpicking the current tab opens
- the next picked tab, or home if none remain. The named action `pick-tab` is
- rebindable, for example `[bind] p = "pick-tab"`; that binding replaces Space
- and opens the switcher from the board. A different action bound to Space takes
- precedence.
+ The dense tree keeps Common pool first, then pinned squads and the selected sort
+ order. Expand a squad to see its members; the lead is marked and included once in
+ its count. A collapsed squad retains its worst observed member state. An identity
+ in several squads keeps each squad's state and one conversation/draft by UUID.
+ Common pool means no Ops squad membership, not no Core room membership. Partial
+ or failed reads never prove that an identity belongs in the pool.
- Unpicked squads share one `N not on this board` segment, dim when quiet and
- lit with ◆ waiting and ✗ blocked counts when they need attention. Click it to
- open a switcher limited to those squads. Picks belong to this board process,
- survive refresh and resizing, and never write `ops.toml`. `tabs.hide` remains
- global: hidden tabs stay out of the tab line even when opened or picked.
+ With tree focus, ↑/↓ move, Right expands and Left collapses; Enter inspects the
+ selected target. Tab moves between the tree and conversation. `1`/`2`/`3` switch
+ sections only when an editor, search, completion, overlay or conversation has not
+ claimed the key. `s` cycles sort and `S` reverses; `p` pins/unpins a squad, `n`
+ creates a squad, `m` manages a squad or assigns a member, `x` removes only the
+ selected membership, `D` opens Digest and `c` opens Timers. Member rename has no
+ `r` hint or default binding. `w` is reserved for snapshots and performs no action.
+ `j` jumps to the selected agent's host when available; it is not tree navigation.
- The line keeps the current tab visible. Left overflow shows `‹ N`; right
- overflow names hidden tabs as `+N › remote◆2 docs …`, waiting first, then
- blocked, then quiet, retaining arrangement order within each tier. Names remain
- full unless a visible group prefix makes them unambiguous. If configured pins
- leave no room for the current tab, pins step aside from the end, except the
- current pin; their stored order stays unchanged. A label wider than the available
- cells is shortened with `…`. The shown name keeps semantic marks and counts
- when a name grapheme fits. At the minimum name width, a clipped grapheme can
- replace the ellipsis. Otherwise the ordinary prefix is fitted; at one cell a
- leading attention mark wins over the ellipsis. Selection is conveyed by the
- background, bold and no-background reverse styling. A named tab keeps its fixed
- attention slot outside the bar and one blank cell on each side of its name/count. Hidden tabs opened through the switcher remain
- selected and marked `(hidden)`, without a drag target. There are no number keys.
+ `ctrl-r` refreshes without discarding entered text. `/` searches. `?` help and the
+ footer use the same effective binding table as dispatch: defaults, global `[bind]`,
+ host overrides, then section overrides. Compatible explicit bindings keep their
+ meaning, including an explicit `f5 = "refresh"` binding. Whole optional hints drop
+ when space is short; help remains available.
+ Menus, completion and editors own their keys and consume close without replay.
+ Pointer controls use only their current visible rectangles.
- `ctrl-r` refreshes the board in squad, leads and all views, including while
- searching or composing a message, without changing the entered text. The footer
- and `?` help list the effective bindings. The footer shows `↑↓ move`, the row's main
- action (`⏎ open`), `t talk` and `e expand` where those keys are bound and the selected
- row allows them. `v view` appears only for an expanded row with a reply, followed by
- `/ search`, `? more` and `q quit`. Keys are bold Accent, labels are Muted, and
- NO_COLOR keeps keys bold. `a write` and `A ask lead` stay bound and appear in `?`
- help and context menus. When width runs out, whole optional hints drop from the end;
- `q quit` and `? more` always stay. Other keys remain bound and listed in `?`;
- `s switch` joins the footer when the tab line hides tabs. A `talk` or `reply`
- binding of your own keeps its hint. Rebind in `[bind]` (or a section), or
- `[tabs.all.bind]` for all. F5 has no default action; an explicit
- `f5 = "refresh"` binding remains supported. Composing, search, menus and overlays
- keep their own footer; the board's key hints are blank under a menu or overlay.
+ Old squad tabs, Left/Right tab navigation, tab reorder/pick/hide, factory views and
+ custom pane geometry do not select a legacy UI. The board reports located
+ migration notices and preserves authored TOML. `--tabs` is refused; `--squad`
+ selects that squad in Agents. Independent `ls --tab` projections remain available.
+ See [the migration table](../../docs/ui-migration.md) for old-to-new controls.
Ordinary canvas, content and chrome inherit the terminal background. The board
uses shared TMT tokens: `text` for shown tab names, primary counts and
partial/unavailable explanations, `muted` for context and key hints, and `dim`
for separators and incidental metadata. HOME summary state glyphs keep their
- semantic roles; their words and counts use `text`. Tab marks retain configured
- waiting/blocked colors, independently of neutral names. Shown top-level names
- use the theme's `selection` background with bold, including
- HOME. Row headings and tasks retain their blank indentation; selection adds no
- prefix glyph. Pane tab names also have no added decoration. Requested targets are underlined and say `Opening`; the shown document
- keeps selection until the request loads or fails. A terminal without a selection
+ semantic roles; their words and counts use `text`. Selection uses the theme's
+ background and readable text, with words and marks independent of colour. A terminal without a selection
background uses reverse video, including `NO_COLOR`. On a real selection
background the final word policy uses readable `text`, while semantic marks
retain their colors. User theme and tab-color overrides still apply. Colors
reinforce words and marks; never infer state from color alone. The CLI theme is `theme.base` in the global `config.json`;
`tmt config show` shows its value and file. Board themes layer that resolved
theme, then `[board.theme]`, then `[squad.<name>.theme]` in `ops.toml`.
`auto` works in both `ops.toml` theme layers and both picker scopes; the global
`config.json` theme rejects it. Use `tmt ops sq theme set auto` for all boards.
`tmt ops sq theme ls` (or bare `tmt ops sq theme`) lists built-in bases, marking the
current base and its source: `default`, `cli`, `board`, `squad` or `detected`.
`auto` is first and is the board default when no layer sets a base. It chooses
`tmt` or `tmt-light` from COLORFGBG, then an OSC 11 query only when opening an
interactive colored board, with a 100 ms limit and dark fallback. Concrete
configured bases win. Startup keys received during the query are discarded;
late replies never become board actions. Lists never query: without COLORFGBG,
`auto` says “matches the terminal when the board opens”, with JSON
`resolvedBase: null`; a measured result says `auto (tmt-light)` or `auto (tmt)`
and `detected`, retaining its configuration layer in `baseSource`. Add
`--squad <name>` to inspect that squad. These choices affect the board only;
CLI colors stay unchanged.
```sh
tmt ops sq theme set auto # match the terminal on all boards
tmt ops sq theme set tmt-light # all boards
tmt ops sq theme set mono --squad product # this squad
tmt ops sq theme rm --squad product # remove only its base override
```
Set and remove keep token overrides and the rest of the user's TOML. They
refuse if the file changed since it was read. On the board, the `,` settings menu's
Theme row opens the theme picker (`theme` is also a bindable action, with no default key). Arrow keys or j/k preview in
memory; Tab switches all-boards/this-squad scope, Enter saves, and Esc cancels.
- The leads/all tabs offer all-boards scope only. A squad's own base still wins
+ Overview and Common pool offer all-boards scope only. A squad's own base still wins
over an all-boards preview; the picker names that masking setting. A failed
save stays open with a notice; cancel and reopen to read a changed file.
Agents change the user's appearance only when the user requests it.
- When the squad tab's lead row is selected, detail shows its name with a dim `lead`
- tag, state/model/cap, task, nonempty pending as `◆ waits on you`, and links.
- Missing values are omitted. With no row fields set, it shows
- `no row fields set · tmt ops sq set <lead> task=…`.
- Lead detail reads and displays neither the notebook nor reply bodies; the pane
- headers name the other panes.
-
- For members, the detail pane shows full projected board-column values not already shown by its header, task, activity or links, in column order; values wrap without grid truncation, with `?` for failed providers and `–` for missing values.
-
- The replies pane shows full available replies to your squad requests as safe
- Markdown, using the notes pane's styles. Reply bodies are indented; prompts wrap,
- and recipient/age headers stay on one line. Fenced code and unsupported Markdown
- constructs appear as source text. Focus replies to scroll with arrows or j/k,
- PgUp/PgDn and Home/End, or use the wheel over the pane. The scroll marks show
- remaining content. Older replies without a loaded body retain `tmt result <id>`
- hints; reading and scrolling acknowledge nothing. Only recipient finals
- (`retained`, `expired`, `unavailable`) appear here. An originator withdrawal
- remains in Core request history with its reason and time; it is neither a
- recipient reply nor approval and does not clear independent manual pending/state.
-
- Rows that wait on you show a single decision line: `pending` when set,
- otherwise the oldest unanswered request preview. Text ends in `…` when it does
- not fit; detail retains the full available text and the effective reply/jump
- keys. Request age comes from the inbox timestamp; pending-only rows have no
- request age. The hint uses the tab's member count and drops its oldest-member
- label first when space is short.
-
- Press `A` (`ask-lead`, rebindable) on a squad tab to open the docked prompt
- with recipient-first header `→ lead <name>`.
- The prompt starts with "List what waits on me: one line each with who, the
- decision, your suggestion and what happens if I wait." Edit before Enter sends;
- Esc cancels. Missing or changed lead/sender/squad refuses without sending.
- It uses ordinary detached `tmt talk`; replies and ▚ notices follow the normal
- request path.
-
- Set the question with `[board] ask_lead`, overridden by
- `[squad.<name>.board] ask_lead`. It must be a nonempty single line of at most
- 4000 characters. The settings editor and `tmt ops sq config set board.ask_lead`
- use the same validation and concurrent-edit refusal as other board settings.
+ The selected agent's conversation header shows its name, state, observed presence,
+ runtime/model, supplied Digest labels, timer observations and resolved link when
+ available. Missing observations stay unavailable. Read the independent `sq ls`
+ JSON or `sq status` for full custom fields and domain metadata; those CLI fields
+ do not create a second detail grid or notebook pane.
- ```sh
- tmt ops sq config set board.ask_lead "What needs my decision?"
- tmt ops sq config set board.ask_lead "Summarize our pending decisions." --squad product
- ```
+ The response region shows the selected agent's latest available response as safe
+ Markdown. Scrolling, copying and source selection send and acknowledge nothing.
+ A response that is unavailable or still loading is labelled; a withdrawal is not
+ a recipient reply or approval and does not clear manual pending/state. Overview
+ owns global attention; selected-target context never substitutes a guessed
+ recipient for a hidden, removed or unknown target.
- Press `a` on a home, squad member or leads row, including its expanded `e` band, to write to it. One composer
- answers an open request when something waits on you and otherwise sends the squad's
- lead a note (or opens talk when no lead is available); Tab cycles **answer, note, talk and status** (talk is a detached `tmt talk`:
- a request that expects a reply), through the modes the row allows. The input opens
- directly beneath the complete selected row in an opaque full-width band, shifting
- rows below it. The first line names the recipient, its squad and the mode: `→ docs-sweep (tmt-product) ·
- answer`, `→ docs-sweep (tmt-product) · talk`, or `→ sol (tmt-product) · note · about
- docs-sweep` for a note to the lead about that member; when the row itself receives
- the note it is `→ docs-sweep (tmt-product) · note`. The chosen waiting question is quoted above the answer input. Several open
- requests require an explicit choice before composing. `r` has no default key
- (`a` already answers first; `reply` stays bindable and, on a row that waits only on a
- `pending` decision, opens a note to that member: nothing is sent, cleared or
- acknowledged until you press Enter on a non-empty note, and the pending text stays
- until it is explicitly cleared by its owner or Update status). `t` opens talk mode on every tab. Explicit member-note
- bindings retain that recipient and cycle note, talk and status.
+ `t` opens Talk for the selected agent through the shared conversation composer.
+ Writing requires a recorded sender. Common pool supports Talk without inventing
+ a squad; notes and manual squad status require a real membership. Explicit
+ `annotate`/`reply` bindings reuse the same validated composer for squad members:
+ real request IDs answer through `tmt answer`, and notes retain the actual lead or
+ explicit member recipient. Multiple open requests require an explicit choice.
+ The recipient and source are frozen and rechecked at submission. Esc cancels and
+ empty input sends nothing. No member `r` default is provided.
- Use `a → Tab → status` to **Update status** for the selected lead or member. The
+ Use a squad member's shared composer and Tab to **Update status** for the selected lead or member. The
shared band shows exact raw manual `pending` and `state` values separately from
**Requires a reply**. Up/down selects a field or action; Enter toggles **Clear
pending** or **Replace state**. Type the explicit replacement on its row and a
reason on **Reason**, then choose **Apply and notify**. Neither change is
preselected. Clearing pending never infers a new state. PgUp/PgDn scroll long
previews; old/new values stack at narrow widths. Tab preserves both the message
draft and the separate status form; Esc cancels.
Every selected field participates in the atomic apply with its exact raw value
expectation, including locally unchanged selections. The previewed identity UUID,
room, namespace and metadata keys remain fixed. Only actual changes are announced.
A conflict changes nothing,
refreshes the preview, clears field choices and requires another explicit submit. Legacy empty or invalid
metadata refuses with an unsupported-value notice. An unknown apply outcome sends
no notification and cannot be replayed from the form: inspect current metadata
before reopening. Confirmed updates queue an attributed announcement to that UUID;
it does not require a reply or prove delivery. **Status updated; notification
failed** offers **Retry notification only** when acceptance has not settled; select
that action and press Enter. The
retry retains the same operation and message and never repeats the metadata update.
**Answer request** opens the existing answer composer for one identified unanswered
request; an explicit dismissal is a final reply. Clearing manual fields leaves
request attention intact, and answering leaves unrelated manual status intact.
Several requests keep the explicit picker; Tab can leave it for the other modes
without choosing a request. No status action acknowledges requests.
Inside the message band, Enter sends and Esc cancels; Tab cycles answer, note, talk and status
where they apply, preserving your text. The band has a visible rule in both themes
and NO_COLOR. At 80 columns the quoted question truncates before the recipient
or input, which retains at least 30 columns. A successful send closes the band
and marks the row until the next key; the cursor stays with it. Talk and notes
report what Core did with the request, naming its ID: `✓ sent` (written to the
pane), `✓ queued` (in the inbox), `✓ queued · offline` (no live session),
`✓ held` (the recipient's digest checklist holds it) or `✓ handed over` (no
receipt; do not resend). A failure after Core accepted the request names that
retained request, and the board never resends it.
Opening, cancelling, empty input or a changed target sends nothing. A failed
send shows its error and does not show `✓ sent` or retry automatically.
## Home dashboard
- The built-in `all` board shows needs-you squads first, then provider meters and
- one muted longest-window usage line, grouped leads, squads and cron. `◆ Needs
- you` names waiting squads; known empty attention says `✓ Nothing needs you`.
- Marks and words identify working/idle/blocked totals, with partial/unknown notices
- retained. One blank precedes Leads and Squads; each rule heading starts `── `,
- with one rule per boundary. Cron is one muted line after the squads table.
-
- The usage line is `Usage 1h N tok · top <member> P%`, using actual global
- `[board] tok` labels and its longest window. UUIDs in several sampling squads
- count once using the better-covered observation. Missing readings are counted
- as members without data; incomplete totals/shares carry `~`, measured zero is
- `0`, and a zero total has no share. Model attribution is best effort. Search
- limits observations to shown squads. Short windows, missing-data coverage and
- shortened metadata are available through `e` on the overview, not repeated when
- already visible. Reading acquires and sends nothing.
+ Overview is the shell's global dashboard. Waiting and blocked observations remain
+ visible; a complete read can say `Nothing needs you`, but a partial/stale read
+ shows observed counts and says totals are unknown. Agents/Digest keep contextual
+ headers instead of repeating global counts. Search, pinning, sorting, expansion,
+ refresh and reload retain exact room/identity selection. A hidden or removed
+ selected target cannot redirect an operation to an adjacent row.
- Squads use one full-width row with aligned squad/lead/members/longest-window token
- and `lead %` headers (the lead’s fraction of its squad window). At 60 columns,
- lead/member cells move to shared detail; token and lead percentage remain. Missing values differ from zero and partial evidence; sampling
- off hides token cells. Mixed window settings label actual durations. `e` reveals
- hidden short windows, model and member counts from held observations. Blocked
- rows retain member, squad and `observed` age; questions stay in the `a` composer.
- Public `tmt ops sq ls --tab all --json` and text retain the aggregate document.
+ The longest-window usage line uses the configured global token window. Identities
+ in several sampling squads count once with the better-covered observation.
+ Missing differs from measured zero; incomplete totals/shares carry partial
+ coverage, and a zero total has no share. Sampling off supplies no token values.
+ Model attribution is best effort. Provider limits and sampled history reuse the
+ existing bounded observations; reading sends and acknowledges nothing.
+ Public `tmt ops sq ls --tab all --json` retains its aggregate document.
- Leads show the latest exchange with you in one full-width group between horizontal
- rules, with blank side margins. Each header has a bold name, its squad from 80 columns, and a labelled last-exchange age
- at the right. The header marks the latest exchange: ◆ means the lead asks you,
- … means you asked and no reply has been submitted, and ✓ means the lead replied.
- A lead with no exchange has a blank mark and `–` age. Leads marked ◆ come first,
- oldest ask first; other exchanges follow newest first, then undated exchanges,
- then leads without an exchange ordered by name. Reply previews start collapsed.
- `e` expands/collapses that row's fields and latest reply using the same presentation
- as [member rows](#squad-member-list). Reading sends and acknowledges nothing.
- `t` talks to the selected member or lead; `a` keeps its answer/note action.
+ Select a squad to inspect its lead, members, usage and timers. Add a member, set or
+ clear its lead, rename it, pin it or open Timers through the named controls or
+ Manage. `n` creates from a name, optional lead and explicit members. Assign offers
+ add (keep every membership) or move (remove the selected source only after the
+ destination add is confirmed). Common pool offers add only.
- The `→ all leads` footer sits outside the group. `A` writes to all current leads;
- `@` picks one lead. Both use the ordinary composer. The recipient identities
- are frozen when it opens, deduplicated for dispatch and checked again before
- sending. A changed sender or lead audience sends nothing. Feedback reports
- each recipient's queued or unavailable acceptance; uncertain acceptance keeps
- its operation ID and saved intent for inspection, with no automatic resend.
+ Management reviews freeze exact room/identity UUIDs and revisions, show timer
+ impact and start on Cancel. Tab reaches the scrollable read region and Apply;
+ clipped or stale Apply controls cannot submit. Pending prevents repeated applies.
+ Results retain per-item success/failure and explicit repair guidance, with no
+ automatic retry. `x` removes membership, not an identity, process, draft or history.
+ Source timers pause before leave; re-add/reassign never resumes them. Explicit
+ Resume is separate.
- One cursor spans overview, attention rows, leads, their footer, squads and cron.
- The overview is the first traversal stop; its selection appears only on usage,
- never on meter bars. `e`/Enter opens its shared detail; search hides this stop. Arrows or j/k move it; Tab
- keeps its board-wide pane-focus behavior and does not jump between home sections. Home opens on its first row from the top:
- the first blocked row, else the first lead, else the first squad. Switching back to home from
- another tab returns to the row you left. Squad tabs keep their own start on the lead. Enter jumps to a member/lead, opens the squad, or composes on the all-leads footer. `a` answers an open request
- through public `tmt answer`, otherwise annotates for that squad's actual lead.
- The composer refuses changed targets/requests/leads and missing sender/lead;
- Esc cancels and empty text sends nothing. Left/right switch tabs, `s` opens the
- switcher, and `/` searches. Home has no r/R reply shortcut or numeric navigation.
+ Squad rename keeps its room UUID, moves Ops metadata/config and the cron counter
+ namespace/jobs' squad field, and preserves c-ids and pause. A second-step failure
+ leaves the confirmed room name intact and reports a repair command. Use that
+ exact command to repair; do not roll the room name back or blindly retry.
## Digest time
`tmt ops sq digest <member> [duration|off] [--squad <name>] [--json]` shows, sets or clears
one active member or lead policy. Showing, setting and clearing all require the
recorded user or the current squad lead; a lead needs the saved owner recorded with
`tmt ops sq me <owner>`. Human output names the member. JSON includes `member` (the
resolved name) and `identityId` (the UUID), alongside the policy fields.
Use whole `s`/`m`/`h` segments from **1s through 24h**, such as `30m` or `1h30m`.
Zero, negative and larger windows are refused. Omit duration to inspect; `off` clears.
Running it again replaces the window against the observed revision. A revision
conflict says to reload and retry; there is no automatic retry or recurring cadence.
Core owns digest and held items; Squad stores no policy. The command passes through
Core errors, including when an older Core has no digest operation. Digest delays
ordinary deliveries; owner and urgent messages can still interrupt.
## Cron on the board
- The home tab shows one line, `cron · N jobs · next <time> <owner> · clock on/off/checking · c list`: the job count
- and earliest active slot. It omits the job prompt, clock location and paths. Up/Down
- reaches it like any row. Enter on it, or `c` anywhere, lists every squad's
- jobs, hidden squads included; Enter opens the job's squad and Esc closes. The
- job list retains full prompts and clock status (`no clock` means due slots are
- not sent; `clock: checking…` is the first read). A running clock identifies its
- holder as `session:window` inside tmux when available, otherwise by pane id.
+ `c` or the selected squad's Timers control opens the shared job list, scoped by
+ room UUID. Its title names that exact squad even when empty; an unavailable room
+ is labelled by UUID rather than as all squads. A selected member prefills its
+ owner when creating a job. Overview and
+ Common pool do not invent a room; choose a real squad for a new job. Read failures
+ retain prior jobs and their reason. `no clock` means due slots are not sent;
+ `clock: checking…` is the first read. A running clock shows its holder's
+ `session:window` when available, otherwise its pane ID.
- A squad tab is split in two: members above, that squad's jobs below (as tall as its jobs, at
- most two fifths of the body). The `c` list is as tall as its jobs too. Tab moves into the
- jobs after the last pane and back to the first. Members who own an active job show
- `cron <next>` at the row end (the first thing to drop when narrow) and in their detail. Jobs start collapsed; `e` toggles their shared [row detail](#squad-member-list) in place. Selection alone never expands them.
+ The focused list owns Enter (open the job's squad), `n` new, `e` expand/collapse, `E` edit, `p`
+ pause/resume, `x` send now, `o` reassign and `d` delete after confirmation. The
+ workspace entry follows explicit bindings; the modal owns its scoped keys.
+ Forms retain exact owner/message/schedule;
+ Esc cancels. Multiline messages use the shared editor; oversized stored messages
+ remain intact when editing only owner or schedule.
- While the jobs (or the `c` list) have focus these keys are job keys, and `?` lists them:
- Enter go to the owner (open the squad, in the list), `n` new, `e` expand/collapse, `E` edit, `p` pause or resume,
- `x` send now, `o` reassign and `d` delete (after a confirmation). A key you bound in `[bind]`
- keeps its binding. New, edit and reassign use the input line one step at a time: owner
- (member name), message, then schedule (`every 3h from 09:00`, `daily 09:00`, `weekdays 09:00`,
- `mon,thu 10:00` or five cron fields); Esc cancels and nothing is written. The message is sent as
- typed, and an edit leaves untouched fields as stored. Multiline messages use the shared
- editor below; messages above its 1 MiB editing limit remain intact while owner and schedule
- can still be changed. Changes
- use the same permission and revision checks as the commands: only the recorded user or the
- squad's lead can change jobs, and a job that changed since you looked is refused, not
- overwritten. Failures are shown and never retried. `x` sends once, like `tmt ops sq cron send`.
+ Changes use the same permission/revision checks as the CLI: only the recorded
+ user or squad lead can change jobs; changed jobs are refused, never overwritten.
+ Failures remain visible with no automatic retry. `x` sends once through the same
+ manual service as `tmt ops sq cron send`. A missing owner is shown as
+ `paused · owner missing`. Re-add/reassign never resumes a paused job; only explicit
+ Resume clears pause.
## Manage checklists
`tmt ops sq checklist ls --room ROOM_UUID` reads a manually authored room checklist;
`--json` returns one complete document. `list` remains an accepted hidden alias;
help and examples use `ls`. These commands are also available as `tmt ops squad checklist`.
The board's Checklist actions use the same admitted service. Every command requires the exact room UUID,
never a squad name; writes freeze supplied checklist/item UUIDs and revisions.
No command accepts an actor override or uses a member filter as assignment.
| Action | Explicit inputs besides `--room` |
| ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ls` | Optional `--include-archived`, `--completion open\|complete`, `--assignee UUID`; reads create nothing |
| `show` | `--checklist UUID --item UUID` |
| `create TITLE` | `--checklist UUID --item UUID --expect-inventory absent\|POSITIVE`; optional `--body TEXT --reference HTTP(S) --assignee UUID` |
| `edit` | `--checklist UUID --item UUID --expect-revision POSITIVE`; at least one `--title TEXT`, `--body TEXT`/`--clear-body`, `--reference HTTP(S)`/`--clear-reference` |
| `assign` | Item/checklist UUIDs, exact `--expect-revision`, explicit `--assignee UUID` |
| `unassign`, `complete`, `reopen`, `archive`, `restore` | Item/checklist UUIDs and exact `--expect-revision` |
| `delete` | Item/checklist UUIDs, `--expect-revision`, `--expect-inventory`, matching `--confirm-item UUID --confirm-revision POSITIVE` |
| `reorder` | `--checklist UUID --expect-inventory POSITIVE --order JSON_UUID_ARRAY`; all nondeleted items including archived items |
Generate new checklist/item UUIDs before the first Create and keep them fixed for
that submission. First Create expects `absent`; subsequent Create expects the
reviewed positive inventory revision. No command invents new UUIDs, adopts a
latest revision, retries or substitutes a same-name target after refusal.
Set/clear pairs conflict. Empty body normalizes to absent; empty reference is
invalid and clearing it is explicit. Title is nonblank, without controls, at most
1024 UTF-8 bytes; body is at most 65536 bytes with only line break/tab controls.
A single reference is inert HTTP(S), at most 4096 bytes, never fetched or opened.
Authored strings remain data.
The verified invoking caller decides authority. Only when no caller is bound may
the recorded active user be used; ambiguous or invalid callers refuse. Active
recorded users and room leads are managers. Collaborators can create unassigned
items, edit content, complete and reopen unarchived items. Initial assignment
requires manager permission, including self-assignment; only managers assign,
unassign, archive, restore, delete or reorder. Assignment chooses an exact active
same-room member UUID. Departure retains its UUID/last label as unavailable;
Unassign needs no live former assignee. Completion changes no pending/state/request
or dispatch/notification data.
Each actual item change increments only its revision; Create/Delete/actual Reorder
also increment inventory once. A no-op changes neither revision but still admits
permission and exact expectations. Archive retains content, completion and order;
Restore changes only visibility. Archived items require explicit Restore before
editing, assigning or completion changes. Delete removes authored content and
retains a minimal tombstone; neither deleted nor live UUIDs may be reused.
Success exits 0, checklist operation errors 1 and grammar errors 2. JSON successes
have `action` and `current`; listing uses semantic action `list`, `totalCount` before
filtering and `matchedCount` after filtering. Mutations add `changed`, plus `itemId`
and `itemRevision` where applicable. `current` contains `room:{id,name,available,manager}`,
nullable `checklistId`/`inventoryRevision`, ordered `items` and nullable
`deletion:{itemId,deletionRevision}`. Items expose `{id,revision,title,body,reference,
assignee,completion,archived}`; absent optionals are null and assignment is null or
`{id,label,available}`. Completion is `open` or `complete`.
Errors retain `{error:{code,message,current?}}`. Only authorized typed conflict/
deletion data supplies `current`; denial exposes no current projection. Human
errors go to stderr and print no success line. Missing checklist, empty inventory,
filtered-empty inventory and storage/read failure are distinct; corrupt data is
never treated as empty or repaired. Exact UUID reads preserve a renamed room;
a same-name successor is distinct. Only the recorded active user may inspect an
unavailable room's retained data read-only, and all orphan writes refuse.
After `CHECKLIST_CONFLICT`, review the supplied current revisions and make a new
explicit submission. `CHECKLIST_OUTCOME_UNKNOWN` never claims success or rollback
and never permits blind replay: later matching fields, revision or tombstone only
confirm observed current state. Current state refreshed; original update outcome
remains unknown. No operation ledger is introduced.
## Manage recurring jobs
`tmt ops sq cron ls [--squad NAME]` lists jobs across all active squads, including
hidden boards. `show <squad> <id>` shows the exact message, pause attribution and
up to three future slots. All management commands take `--json`.
```sh
tmt ops sq cron add product worker --every 30m "Review the queue"
tmt ops sq cron add product worker --at 09:00 --on weekdays "Review the queue"
tmt ops sq cron add product worker --cron "0 */3 * * *" "Review the queue"
tmt ops sq cron edit product c1 --message "Review pending changes"
tmt ops sq cron pause product c1
tmt ops sq cron resume product c1
tmt ops sq cron reassign product c1 reviewer
tmt ops sq cron rm product c1
```
Writes require the recorded user or that squad's current lead. `--identity`
selects an explicit actor; otherwise the verified caller wins, then the recorded
user only when no caller exists. An ordinary identified member cannot fall back
to the user. Add accepts `--paused`; edit accepts the same schedule flags or
`--message`. Reassignment clears no owner but retains its pause; resume separately.
Messages must be nonempty and are retained exactly, without substitution. Jobs
live under the extension's own storage, separate from `ops.toml`. IDs are never
reused. Fixed local times skip daylight-saving gaps and use the first repeated
time; intervals keep elapsed duration. Schedule edits retain the stored zone.
Change notices are best-effort announcements to the owner; reassignment notifies
old/new owners, and the actor's own notice is suppressed. A committed change can
report notification warnings. Owner retirement pauses the job as no owner and
notifies the lead; each management invocation handles at most 16 pending hooks.
An open board keeps the clock running independently of its refresh setting.
Outside a board, `tmt ops sq cron run` keeps it in the current pane; `clock` shows
its pane, PID and how long it has held the lease. Only one clock holds the lease. Stop `run` with
Ctrl-C; stale leases can be taken over. Startup and takeover begin at now, so
slots missed while no clock was running are not caught up. A running clock
admits slots since its previous tick, capped at five minutes. `tick` makes one
pass over the last 60 seconds and refuses an active clock. Scheduled messages
are anonymous requests; the same room/job/slot operation is accepted once.
`tmt ops sq cron send product c1` sends once now using the recorded or explicit
user/lead actor. It can send a paused job with a current owner and changes no
schedule. Each explicit send is a separate action. Output confirms acceptance,
not delivery or completion; Squad stores no run results. If acceptance is
uncertain, retain the reported operation ID and recover it with `dispatch.show`
through `tmt api` before deciding on another action. Board cron controls (above) are
separate from the clock lifecycle.
## Use the board checklist
- Press `,`, choose the first row **Actions…**, then **Checklist**. A configured
- `menu` binding opens the same action menu. A named squad uses its exact room;
- HOME, leads and aggregate views ask you to choose a room explicitly. Opening
+ Press `,`, choose **Actions** after the layout/Theme/Token window choices, then
+ **Checklist**. A configured
+ `menu` binding opens the same action menu. A visible selected squad or its member
+ in Agents or Digest uses its exact room; Overview, Common pool and hidden/missing targets
+ ask you to choose a room explicitly. Opening
Checklist does not select or assign a member. Esc from the action menu returns
directly to the board.
The checklist starts with Open items, Squad-wide assignment scope and archived
items excluded. Tab/Shift-Tab reaches **Filter** and **Actions**; Enter opens their
choices. Filters include Complete, All completion, Unassigned, an explicit member
and archived inclusion. Counts show visible items and all nondeleted items in the
same archive scope. Arrows/j/k, Home/End and PgUp/PgDn select or scroll items;
Enter opens details without completing an item. In details, arrows and paging keys
read the content; Tab switches between reading and the named action choices.
Actions offers Create and Refresh, and Reorder for a current manager. Create starts
unassigned. Details offers Edit, Complete/Reopen and an explicit Open reference;
current managers can Assign/Unassign, Archive/Restore and Delete. Archived items
expose Restore/Delete. Reorder includes the full inventory, including archived
items, and uses Move up/Move down followed by a separate preview and Confirm.
Forms accept typing or paste; Tab moves fields and Ctrl-U clears a field.
Every update previews exact actor, room, checklist, item and required revisions.
Delete starts on Cancel; enlarge the terminal when its exact target and controls
cannot fit readably. Conflicts and refusals retain the draft and target. Refresh
reads current state, then Review explicitly adopts its expectations before a fresh
Confirm. An original unknown outcome stays unknown after observation or later
updates. Esc from a child screen retains intent and returns to the list; Esc from
the list returns to the board. Resume retained work through Actions, or explicitly
Cancel to discard it. Checklist completion changes no member state, requests or
attention, and opens references only when you choose Open reference.
## Inspect board settings
- Press `,` to open settings for the shown squad or tab; `settings` is bindable.
- Select with arrows/j/k, PgUp/PgDn or Home/End; the wheel scrolls. Press Enter on
- an editable entry to open its value. Type a scalar or JSON array, or use Ctrl-U
- to clear it. Valid input previews live behind the prompt; Enter saves, and Esc
- cancels the edit without writing. A second Esc closes settings. Entries marked
- `*`, including provider argv and run bindings, are read-only. Each value shows its preset/default or configuration setting source
- and the path of `ops.toml`. Configured provider argv and run bindings are
- shown without executing them. Close and reopen to read later config edits.
-
- Board preference choices save to the all-boards layer of `ops.toml` by default
- and persist across sessions. Theme and view pickers still offer an explicit
- `this squad` scope. A hand-written squad key overrides the shared value: its
- settings row carries `≠` and `this squad · r reset`. The header counts explicit
- settings that differ from all boards. Press `r` on that row to remove only its
- squad key and use the shared value; other rows ignore `r`. Comments and unrelated
- keys stay intact. A failed or conflicting write leaves the effective value unchanged.
+ Press `,` for Workspace settings: Auto/Side/Stack, Theme, Token window and
+ Actions. The menu reports current choices; cancel writes nothing. Layout, pins
+ and sort are per-user Ops preferences. Config saves compare the opening values
+ and file bytes, preserving comments and unrelated keys; a conflict keeps the
+ current effective choice. Theme can use all-boards or selected-squad scope;
+ Overview/Common pool have only the shared scope.
`tmt ops sq config show` inspects board defaults. Use `--squad product` for one
squad or `--tab all` (also `leads` or a configured tab name) for an aggregate
view, and `--json` for full values and source paths. These scope flags are
exclusive. Inspection changes no configuration or member state. JSON marks the
settings supported by `config set`; the board uses the same validation and writer.
Use `tmt ops sq config set KEY VALUE [--squad NAME]` for validated edits. Squad scope
is required for `layout`, `board.panes`, `board.direction`, `board.sizes`,
`board.hidden_columns`, `notes.render`, `states.STATE.color`, `reminders.enabled`
and `reminders.stale_after`. `board.refresh`, `board.ask_lead`, `board.view` and `board.token_rate.window`
use the squad layer with `--squad`, otherwise the global Squad board layer.
The obsolete `board.home_replies` key is ignored with a one-line deprecation
notice; authored TOML stays unchanged. It is no longer editable. `tabs.order` and `tabs.hide`
always edit global Squad tab policy. Arrays use JSON;
other values are unquoted scalar arguments. Examples:
```sh
tmt ops sq config set notes.render plain --squad product
tmt ops sq config set board.refresh 10s --squad product
tmt ops sq config set board.hidden_columns '["pr_link"]' --squad product
tmt ops sq config set tabs.hide '["leads"]'
```
- Editing `board.direction`, `board.sizes` or `board.panes` pins the effective workflow
- layout and full flat split (direction, panes and sizes) in `ops.toml`, preserving
- the untouched geometry. Future preset changes no longer replace these values.
- Nested split trees are read-only and must be edited in `ops.toml`. Existing validators reject invalid values
- before writing. The writer preserves unrelated keys and comments and refuses a
- file changed since reading it. A conflicting board save keeps the prompt and
- explains the refusal; cancel, close and reopen to load the newer file. Refreshes
- retain the current valid preview and the newest roster data. Provider/run commands,
- patterns and core/provider configuration remain read-only.
+ Legacy geometry, factory view and tab policy remain authored CLI configuration;
+ the workspace reports migration notices instead of applying those arrangements.
+ The validated writer preserves unrelated keys and comments and refuses a file
+ changed since reading. Provider/run commands and Core/provider configuration
+ remain read-only to settings inspection and are never executed by inspection.
For the selected squad, set `reminders.enabled` to `true` or `false`, and
`reminders.stale_after` to whole s/m/h units from 1m to 24h (for example `30m`).
- The settings view shows effective defaults and their sources. Preview reclassifies
- known observed ages without observing or sending reminders; unknown stays unknown.
- After saving, ordinary reload uses the new policy. Disabled reminders produce no
+ `config show --squad <name>` reports effective defaults and their sources.
+ After saving, ordinary reload uses the new policy; unknown observed ages remain
+ unknown. Disabled reminders produce no
stale marks or reminder work. These edits install no provider hook and grant no
extension consent.
## Choose a board view
- `tmt ops sq view ls` (or bare `tmt ops sq view`) lists factory pane arrangements:
- `members`, `team`, `focus`, `notes`, `detail` and `wide`. The default `members`
- view uses one grouped list; the other views use the configured row grid and pane
- arrangements. Workflow states, rows, providers, reminders, the token meter
- and theme keep their settings; crew, pr-queue and minimal remain workflow layouts.
-
- ```sh
- tmt ops sq view set notes # all boards
- tmt ops sq view set wide --squad product # this squad
- tmt ops sq view rm --squad product # inherit the arrangement
- ```
-
- The effective arrangement comes from a hand-written per-squad `board.layout`
- or `panes`, then `[squad.<name>.board] view`, then `[board] view`, then the
- default `members` view. Existing simple direction/sizes configs without a named
- view retain their pane preset. `team` keeps the previous responsive side panes;
- `focus` starts detail/replies/notes folded, `notes` gives lead notes most space,
- `detail` places detail/replies below rows with notes folded, and `wide` uses
- three columns. Team folds detail/replies below 100 cells, detail folds replies
- below 100, and wide folds detail/replies below 180. Manual folds retain their
- existing session rules.
+ The settings menu offers Auto, Side and Stack for the workspace/conversation
+ placement, alongside Actions, Theme and Token window. These per-user Ops
+ preferences persist through Config's compare-and-set writer; stale-file refusal
+ keeps current state and preserves comments and unrelated values. They change
+ presentation only, not workflow state, membership, provider or sampling policy.
- Set and reset write only the selected layer's `view` key through the existing
- format-preserving writer and refuse a concurrently changed file. Scoped set
- refuses a custom layout with a manual-removal hint. Reset removes only `view`,
- retaining custom layout and fold keys; all-boards settings remain masked by
- custom and scoped arrangements. Reset drops an emptied table only when its header
- has no comments; existing empty tables remain. On the board, the `,` settings menu's
- View row opens the view picker (`view` is also bindable, with no default key). Arrow keys or j/k preview only in memory, Tab
- switches all-boards/this-squad scope, Enter saves once, and Esc restores the
- opening arrangement and runtime folds without writing. Data keeps refreshing.
- The leads/home tabs offer all-boards scope only and retain their fixed composition
- during preview, save and cancel; views apply to squad tabs. The default entry
- removes only the chosen layer's view key. A custom entry identifies hand-written layout;
- this-squad preview works, but scoped save is refused with a manual-removal hint.
- An all-boards choice saves while this squad keeps its custom layout; the picker
- names that masking setting.
- A failed or stale save stays open; cancel and reopen to read the changed file.
- Actions…, Theme, View and Token window are rows of the `,` settings menu, which help lists. No preset binds `jump lead`:
- the lead is the first row of its tab. Bind it yourself (`[bind] L = "jump lead"`)
- to go to the lead's pane from any row.
- Agents change views only when requested.
+ Factory `sq view` configuration and custom pane keys no longer choose the UI.
+ Their authored bytes remain intact and located migration notices identify them.
+ There is no legacy mode. Independent CLI list/layout configuration retains its
+ own reader and validation; it is not a second interactive editor or shell.
## Squad member list
- Squad tabs default to one grouped list with the lead first, its `lead` tag, then
- `── members · N ──`. Each member has a mark/name/state/model/age line and its task line. The mark is the
- reported state: `◆` waits on you, `✗` blocked, `◐` review or testing, `●` working, `◌` idle.
- A row that reports no state shows the observed presence in its state cell (`online` or
- `offline`, muted); an offline member that still reports a state reads `<state> · offline`,
- and its name is dim. A bind or unbind shows on the next refresh. A refresh moves no cell: model and age keep their place, a refresh that has not read them yet
- shows the last value dim (for up to ten minutes), and a column widens only when a longer value
- appears, until you resize or change tab. Below 40 columns the model and age are hidden. Waiting members (`◆`) come first, with the oldest questions first; other
- exchanges follow newest first, and members without exchanges sort by name.
- The lead stays first, and authored sections keep their positions. The lead's
- notes stay below the list; `n` hides or shows them.
-
- Press `e` to expand/collapse the selected row in place, including HOME lead/member
- and job rows. Several rows can stay expanded; refresh retains their row identities
- and removes detail when a row disappears. A dim `│` (accent for the selected row)
- attaches the label/value grid below the collapsed row. The collapsed row keeps
- its existing content and selection; the expanded block has no selection background.
- Member fields are task, pending, note, links, model and reply; HOME lead fields are
- task, pending and reply; HOME member fields are task and reply. Empty fields are
- omitted, and fields already visible in the collapsed row are not repeated (such as
- the member task line). A missing reply says `no reply yet`. Job fields are when, next,
- last when available, target and prompt, omitting what the collapsed job line already
- shows at that width. With no additional fields it says `no details yet`. Long fields wrap to three lines; replies
- show sender/age and up to six lines of Markdown, followed by `… N more lines · v view`
- when clipped. Arrow keys/j/k move between rows; the block scrolls with its pane.
- `v` opens any available reply in a read-only full-message reader; clicking the
- clipped-reply hint also opens it. Arrows/PgUp/PgDn/Home/End scroll that reader,
- and Esc closes it without changing row expansion. Rows without replies ignore `v`.
- The footer shows `v view` only for an expanded selected row with a reply; `?` always
- lists it. Enter keeps its jump/menu action and `o` keeps its link action.
- `a` opens the existing answer/note/talk/status composer, and `t` opens talk mode.
- Reading works without recording yourself; writing requires `tmt ops sq me`.
+ Agents shows one expandable squad tree and Common pool, with explicit room and
+ identity targets. Lead badges and member counts include the lead once. State,
+ attention and presence are scoped observations; missing state says `unreported`
+ and failed observations remain partial/stale rather than becoming idle or zero.
+ A collapsed squad shows its worst observed state. Display names never provide
+ operation authority.
- Choose `team` from the `,` settings menu's View row for the previous detail/replies
- side panes; `members` restores the grouped list.
- Hand-written `board.layout` or `panes` keeps its configured composition.
+ Enter inspects the selected agent in the shared conversation; `t` opens Talk and
+ `j` jumps to its available host. Source-provided labels retain their semantic roles
+ and choice actions; a choice click opens that exact control rather than sending.
+ Digest uses the same UUID context and existing Core authorization. Refresh and
+ resize retain the target and clear old hit rectangles. Reading requires no sender
+ and sends/acknowledges nothing; writing requires `tmt ops sq me`.
## Conversation and message editing
- The Replies pane shows the selected agent's conversation. A wide HOME dashboard
- uses the same conversation beside its monitor. Both share response, reading position
- and composer by agent UUID, so renaming an agent or changing its membership does not
+ The workspace shows the selected agent's conversation in its Auto/Side/Stack
+ placement. All sections share response, reading position and composer by agent UUID, so renaming an agent or changing its membership does not
create another draft. Runtime is the observed runtime name, not the model's vendor;
missing runtime/model evidence is shown as unavailable.
A two-pane `rows`/`replies` workspace uses terminal cells without blank row padding.
Its automatic layout is side by side when wide, stacked at medium widths, and focused
single-pane when narrow or low. An explicit side/stack preference degrades safely.
The wheel scrolls the pane under the pointer without changing the message recipient.
PgUp/PgDn/Home/End act on the focused pane. Reading older response text pauses follow;
new output is labelled until End resumes follow. A replaced response stays visible
while paused. A vanished source is labelled separately, not presented as new output.
Talk, reply and annotation use a multiline editor. Left/Right and Home/End move the
caret; Shift with movement selects; Backspace/Delete remove selected text or a
grapheme. Click places the caret and drag selects. Enter submits. Shift+Enter inserts
a newline on terminals that report it distinctly; Ctrl-N is the portable fallback.
Esc leaves input without sending. Direct-message drafts and their quote/paste
attachments are session-only: they survive view/layout/resize and membership changes,
but do not survive restarting the TUI. A successful send clears only the submitted
draft; a failed or uncertain send keeps it.
Bracketed paste never submits. Message text is limited to 1 MiB, with exact text and
newlines preserved. Paste over 500 characters or eight lines folds into one labelled
range; Ctrl-P previews or folds it at the caret, and deletion removes that range.
Name/configuration/search fields keep their smaller bounds and do not gain message
completion. The status reason keeps its 4,000-byte bound and explicit Apply step.
Type `@` in a message, status reason or cron message body to search acquired agents
and squads. Arrows select a candidate; Enter/Tab inserts it, Esc dismisses, and a
click selects it. Mentions are plain text only. The displayed direct recipient stays
unchanged; `@squad` never broadcasts or chooses a lead. Cron stores the literal text,
and later renames do not rewrite it. Colab addressing has its own semantics; a page
link in the conversation does not authorize native page/thread writes.
In the full response reader, `s` opens exact source selection. Shift with movement
or mouse drag selects a range; `y` copies and `a` attaches it to an annotation draft
without sending. The quote carries the response/request identity and source byte
range. Selecting text never acknowledges a request. If the source changes or the
agent disappears, the retained text can still be read/copied but cannot be attached
to another response. Alt-Backspace removes the most recent quote from the composer.
For terminal-native selection, use the terminal's mouse-capture bypass modifier
(commonly Shift); the exact modifier belongs to the terminal.
## Fold board panes
- In split mode, press `d` to fold or expand detail and replies together when
- both panes exist; otherwise it toggles whichever exists. With neither it does
- nothing silently. Footer and help show `d detail+replies ▾` when any is open, or
- `d detail+replies ▸` when both are folded; single-pane labels use that pane's
- title. The footer drops the whole hint when space is short. Click a pane's title
- to toggle it alone.
- A folded title reads `▸ detail` and stays in place. Stacked panes reserve one
- line; side-by-side panes reserve a compact title-width column. Expanded neighbours
- share the freed space, and expanding restores the configured proportions.
- Nested percentages use the raw fractional parent, then round cumulative boundaries
- to terminal cells. For example, Team at body height 21 gives detail/replies 6/7
- cells rather than halving an already rounded parent into 7/6.
- Tab skips folded panes. Folding a focused pane moves focus to rows when visible,
- otherwise the next expanded pane; unfolding keeps an existing focus. With all
- panes folded, only titles and bindings act;
- `n` expands and focuses notes. A single expanded pane stays borderless; bind
- `toggle rows` to fold it, then click its folded title to expand.
-
- Set the initial state or override a binding in `ops.toml`:
-
- ```toml
- [squad.product.board]
- panes = ["rows", "detail"]
- collapsed = ["detail"]
-
- [bind]
- d = "toggle detail"
- ```
-
- `collapsed` accepts unique configured pane names: rows, notes, detail or replies.
- It applies only to split mode. `toggle <pane>...` uses the same literal names
- and accepts one or more unique panes, for example `toggle detail replies`. If any present pane is expanded, it
- folds all present panes; otherwise it expands all. Duplicate or unknown names are configuration
- errors; the action toggles the named panes present on the board and does nothing
- silently if none are present.
- Tabs mode gives a notice before any change. User and section bindings keep their
- usual precedence. Runtime folds survive unchanged refreshes and squad switches
- within the board session. Changed board configuration resets them; restarting
- uses the configured initial state. Manual toggles win over automatic width-based
- folds until the board config changes, including after resizing in either
- direction. Toggling writes no config or member state.
+ The new workspace uses squad expansion and Auto/Side/Stack placement. Left/Right
+ expand/collapse the selected tree group; a hidden or removed selection remains
+ anchored and cannot confer actions on a neighbour. Old pane toggles, fold keys
+ and tab-reorder bindings are retired with located migration notices, preserving
+ user configuration. There is no factory-view or legacy-mode fallback.
## Keep it current
A stale board is worse than none. Update the board as part of every dispatch
and every reply you receive, not later.
```sh
tmt ops squad add <name>... # agents that are already running
tmt ops squad set <member> state=review task="rotate session tokens"
tmt ops squad set <member> pending="approve the token rotation plan"
tmt ops squad set <member> pending= # clear it once answered
tmt ops squad set <member> pr_link=https://github.com/acme/app/pull/412
tmt ops squad rm <name> # leaves the squad; the agent keeps running
```
- `task` describes what the member is doing; `pending` describes what it waits
on the user for. Keep member context in that member's own saved-identity
notebook (`tmt notes path --identity <member>`).
- The per-member `note` field is retired: nonempty `note=` fails before any
writes. Empty `note=` still clears an old value. Reads preserve stored legacy
notes but exclude them from rows and row `fields`; `note` remains reserved
and cannot be reused by a field provider or bound column.
- `pending` is the one decision the member needs from the user. Keep it short
and clear it when it's resolved.
- Field names are `[a-z][a-z0-9_-]*`. Values are one line of at most 1024
bytes. `field=` removes a field.
- `set` applies its pairs in order and reports what it applied. After a
failure, re-run it with the same pairs.
- `tmt ops squad lead <name>` selects the lead independently of free-text `role`
and `lead` fields. Setting or clearing either field never changes leadership,
and selecting a new lead preserves every member's role text and membership.
`tmt ops squad lead --none` clears leadership. Former leads remain members; use
`tmt ops squad rm <name>` separately when they should leave.
- Repeating `tmt ops squad add <name>` reports that the member is already in the
squad and preserves its state and task. A missing state receives the configured
initial value.
- Legacy members with only `role=lead` still appear as lead until a role write
would change leadership or `squad lead` records their separate marker. Listing
and opening the board never perform that conversion. The reserved metadata suffix `lead.marker` is not a
user field and never appears in row `fields`; leadership is shown through
`squad.lead` and the section partition.
- Removing a member clears its fields for this squad only. Its requests and
notes keep the history.
- Squad has no `talk`, `reply`, `replies` or `annotate` command. To talk to a
member use `tmt talk <member> "…" --detach`; to answer what someone is waiting
on you for use `tmt inbox` and `tmt answer` (or `tmt reply --receipt` when you
were given a receipt). Adding a note to a row is the user's board key, not a command, and
agents never call hidden or `__` commands (TMT's CLI style, "Hidden commands").
Without a lead, select one with `tmt ops squad lead <name> --squad <squad>`.
## Keep your notebook current
- The notes pane shows the squad lead's own saved-identity notebook, read-only.
- There is no separate squad notebook. Find your notebook with
- `tmt notes path --identity <lead>` and edit that file with ordinary filesystem
- tools. The board never creates it: a saved lead without a notebook shows
- `(no notes yet)`, while a temporary lead shows the
- `NOTEBOOK_SAVED_IDENTITY_REQUIRED` failure text.
-
- Click a notebook line in the lead notes pane to focus it and place the cursor.
- Arrow keys or j/k move between displayed lines; PgUp/PgDn page, and
- Home/End or g/G select the first/last line. The cursor follows unchanged source
- text when notes refresh (nearest match for duplicates, clamped after deletion).
- The wheel scrolls independently; moving the cursor brings it back into view.
- Every displayed continuation of the selected source line uses the full-width
- selection appearance, including reverse video with `NO_COLOR`. A fixed two-cell
- gutter holds the sent marker or blanks, so notebook text stays aligned.
-
- In Markdown notes, Tab/Shift-Tab select links; the footer previews kind and target.
- Enter or clicking the selected link activates it; the first click selects only.
- Esc clears link selection, and configured bindings take precedence. With no links,
- Tab moves to the next pane. Plain notes and undefined schemes stay inert.
- Built-ins are `tmt:jump/back/talk/answer/open/copy/annotate`; except `back`, append
- `/<current-member-name-or-id>`. Talk/answer/annotate open the existing prompt,
- optionally prefilled by bounded percent-encoded `?text=`; Enter submits, Esc cancels.
- Custom programs require your own `[links]` entries such as
- `gh = "run gh issue view {path}"`: argv only, one argument per template, no shell.
- Bare #N remains plain; full GitHub issue/PR URLs are selectable. Absolute file
- links reveal after resolving symlinks; configured openers get only the containing
- directory. Relative paths stay plain. Opening files requires a custom scheme.
-
- In focused notes, the annotate binding (`a` by default) opens a composer addressed
- to the lead, quoting the line number and a bounded excerpt. Enter sends only
- nonempty text; Esc cancels. The line shows `✎` while your request to the current
- lead is open, clearing after the lead answers and the board refreshes. Notes remain
- read-only. The marker uses the nearest matching quoted excerpt after an edit;
- requests outside the bounded room-history window may not be shown.
+ Each saved member owns its notebook; there is no separate squad notebook.
+ Find it with `tmt notes path --identity <name>` and edit that file with ordinary
+ filesystem tools. The board never creates another member's notebook. The
+ workspace conversation replaces the former Notes-pane navigation; notebook
+ reading/links/line-annotation controls are not workspace defaults.
- The detail pane shows the selected non-lead member's own notebook after its fields,
- using the same read-only Markdown/plain rendering as lead notes. A saved member
- without a notebook shows `(no notes yet)`; temporary members show
- `(temporary identity: no notebook)`. Only the visible selected detail is read,
- on selection and board refresh. The leads/home tabs never show member detail
- notebooks.
+ Notebook content remains owned by each member. A board projection never makes
+ another member's notebook writable; use the explicit notebook commands below.
Every member should keep a short **Current state** section at the top of their own notebook, with
**Now / Next / Blocked** in a few lines, because the user reads it on the board.
Update those lines when the working state changes; keep history below them.
User annotations remain requests about a row or a notebook line.
## Annotations from the user
The user may annotate a row from the board. It arrives as an ordinary TMT
request to you, tagged `[<squad> · <row>] <text>`. You decide what to do with
it: update the board, record it in your notes, or pass it to the member. Reply
to it, because that is how the user sees you handled it.
## Configuration belongs to the user
`ops.toml` sits in TMT's global configuration directory, next to
`config.json` (`tmt config show` prints that path). It may hold `me` (the
user's saved identity, recorded with `tmt ops squad me <name>`) and `me_id` (its
UUID, which lets `me` follow a rename; squad maintains it), each squad's `layout`, the board panes, sections, columns,
states and key bindings. Bindings and actions are the user's. Never edit them
silently. If a change would help, propose the exact lines and let the user
apply them.
On first config/state use, Squad migrates `squad.toml` to `ops.toml` and its
`<dataRoot>/squad/` state to `<dataRoot>/ops/` under a lock. Config comments and
state bytes are preserved; originals become `.migrated-<timestamp>` backups and
user `.bak-*` files stay untouched. If both names exist, Ops wins and a notice
names the ignored legacy paths. An old clock with a live lease defers the entire
migration: config and jobs stay visible on legacy paths, and the notice names the
holder and how to stop it. Invoke again after stopping the clock to migrate.
Restart boards started before the upgrade before editing settings. A legacy config
that reappears is reported but never read or merged; move wanted settings into
`ops.toml`, then delete the old file yourself. Commands use `tmt ops` and the
binary is `tmt-ops`; disposable observations use the `tmt-ops` cache namespace.
Refresh former generated hotkeys with consent using `tmt ops hotkeys install`.
The former generated file and immutable skill sources remain available.
## Observed token usage
- The selected named squad shows completed-request tokens from public core history
- and this board's observations, in **1m / 5m / 1h windows**. The default member grid adds the current
- session model and those three totals, declared in the TEAM/crew preset rows.
- Custom grids opt in with `from = "usage.w1"`, `"usage.w2"`, or `"usage.w3"` on a
- column. Default headers follow `tok`; an explicit `title` stays as configured.
- One-shot `tmt ops sq ls` has no window history. JSON keeps column descriptors without
- usage values; text omits columns whose source is board-only.
- When token sampling is off, usage columns hide and MODEL remains. Default
- member grids keep at least 20 cells for TASK when space permits. On narrow
- boards, inactive window columns step aside first, then PR, then MODEL, and
- finally the active window. Switching the window (the settings menu's Token window row, or a `token-window` binding) also changes which window stays visible
- longest. MODEL follows its content up to 8 cells and truncates longer names.
+ The selected squad's usage disclosure shows completed-request tokens from public
+ Core history and acquired observations in its configured windows (default
+ **1m / 5m / 1h**). The workspace uses those observations independently of the CLI
+ row grid. CLI presets and custom columns can name `usage.w1`, `usage.w2` and
+ `usage.w3`; one-shot `sq ls` has no window history, so JSON keeps those column
+ descriptors without usage values and text omits the board-only sources. Changing
+ the active window affects the acquired summary, not a second member grid.
Input and output count once; cached input is already included in input, and
normalized reasoning in output. Mixed providers sum reported token units, not
cost or interchangeable text volume. Model attribution is best effort: a
mid-session model change attributes retained observations to the current model.
Counters update at request completion and are observed every 5–10 seconds,
not while a model writes. On entry, the board reads closed core history for up to
one hour and continues from its included counter watermark. It stores no separate
usage history and computes no money estimate.
Team enables observation; crew, pr-queue and minimal keep it off by default.
- The all/leads tabs omit this named-squad meter. The settings menu's Token window row cycles the summary's windows, as does
+ Overview and Common pool omit this named-squad meter. The settings menu's Token window row cycles the summary's windows, as does
the bindable `token-window` action (no default key), or clicking any part of the meter.
All three paths save the next window to the shared layer; a squad override remains
- in effect until reset in settings. Member columns show all three at once.
+ in effect until reset in settings. Selected squad usage detail can disclose each configured window.
The live label names the configured window and the live number is a token total. Configure exactly three distinct ascending whole `m`/`h`
durations, from 1m through 24h:
```toml
[board]
tok = "1m/5m/60m" # for example, "5m/60m/24h"
[board.token_rate]
enabled = false
every = "5s" # 5s through 10s; independent of board.refresh
window = "1m" # persisted summary window; falls back to the first configured window
reduced_motion = true
[squad.checkout.board]
tok = "5m/60m/24h" # overrides the global windows
[squad.checkout.board.token_rate]
enabled = true # individual keys override global policy and layout preset
[bind]
- w = "token-window"
+ u = "token-window"
```
`–` means no usable observed interval for that member; a baseline alone is not
measured zero. Covered readings, including measured zero, are numeric. Known nonzero history
deltas also show as partial lower bounds even without continuous coverage;
zero without coverage stays unavailable. `~` marks
a window longer than available coverage or with missing evidence. Windows beyond
one hour include retained board observations when available; partial history
shows the covered total. Core rollups crossing a shorter window boundary are
excluded whole rather than prorated. Unreported members are excluded from
totals and make the total approximate; `?` lists never-reporting members.
Resets, new sessions, invalid counters, gaps and failed reads rebaseline without
- inventing tokens. Returning to a tab refreshes its recent range from core while
- preserving older board observations, without bridging uncovered intervals.
+ inventing tokens. Refreshing the retained usage source keeps older board observations without
+ bridging uncovered intervals.
Changing the observation policy starts fresh observations; core history seeds again
- on the next tab entry.
+ on the next admitted source entry.
- While the usage meter is on, HOME shows provider account meters at every width.
+ While the usage meter is on, Overview shows provider account meters at every width.
Inside, approximately 18 cells show `Claude 36% (7d)`. At 160 columns two bars
share a row; narrower widths give each its own row. Outside, `│ duration` is
time to reset. Short-window readings remain in overview detail.
Warnings are `! ~duration`, or `✗ ~duration`
when depletion before reset is at or within 12 hours. No positive measured burn
means no forecast. Fill reverses the existing state role; Text over the neutral
Track background marks the unfilled portion, never selection. NO_COLOR reverses fill and
underlines track. Overview detail lists only values hidden at the current width.
`~N%/h` averages weekly use over the last 6 hours after an hour of samples.
With enough history, ↗/→/↘ compare recent 1–2-hour pace with that displayed
baseline; the whole-percent precision floor suppresses single-step changes.
Numbers use provider session reports or its captured statusline footer through
Core `check --capture-only`; reads send nothing. A sample older than 2 hours says
`no reading for 3h` (or `no reading yet`), never an old percentage. The board
samples only while open.
Digits count with cubic ease-out for at most 600 ms; reduced motion and summary
window switches show the exact value immediately. Eight bucket-aligned bars show
observed totals by slice: blank is no evidence, ▁ is measured zero and ▂–█ scale
nonzero values. Narrow boards drop the trend, then shorten the unit. The active
window label stays next to the meter values; lead/attention text clips if needed.
Only a terminal too narrow for the compact meter hides it. Without a
covered reading it shows `–` and a dim `no usage reported yet` line. Cycling the window still
saves the shared choice, updates the effective label and posts the window in the board notice. `?`
explains the totals, best-effort coverage, switch order and `tok` configuration.
Whole-hour labels use `h`, so 60m displays as `1h`.
Hover highlights the whole meter group. Hovering one bar temporarily replaces the
number and label with that slice's token total and age, e.g. `18k tok · 3m ago`;
observed zero reads `0 tok`, and no evidence reads `–`. Ages measure from the
slice's end: `now` below one minute, minutes below one hour, then hours. The readout keeps
its fixed width and the hovered bar uses text colour. Leaving restores live text;
there is no popup. Keyboard operation remains available without mouse motion.
## Columns and row lines
Use `[squad.<name>.rows]`; `columns` defines positional tracks and value
sources, and `lines` places cells from track zero. A string names a field,
`""` is an empty cell, and `{ field = "pending", span = 3 }` covers three
tracks. A cell can add `token = "waiting"` (or another semantic theme role),
which overrides its projected field color without changing the value. Literal
colors and legacy color aliases are refused. Missing/empty values and failed
providers without projected colors stay dim; stale-row inheritance and reverse
selection still apply. Spanned cells use the first track's fitting settings. The legacy
`[squad.<name>.columns]` form remains supported; do not set both forms.
`[squad.<name>.board] hidden_columns = ["pr_link"]` hides named original tracks
for the board and `ls` text without deleting columns, field values or authored
lines/spans. Only covered tracks can be hidden and at least one must remain.
A spanning cell shrinks to the surviving tracks in its original range; hiding
one track can shrink a different field's cell rather than remove that field.
Set the mask to `[]` to restore the original grid. JSON retains all field values
and lists the mask when nonempty.
| Setting | Current behavior |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `name`, `title` | Field name and optional column heading. |
| `width` | Cells (1–200) or a quoted percentage (1–100%, supported since Squad alpha.8). |
| `min`, `max` | Cell bounds, including percentages; growing tracks default to a four-cell minimum. |
| `grow` | Weight (0–100), default 0. On the board, grow with max: max wins; the weight has no effect. |
| `align` | `left` (default), `right` or `center`. |
| `truncate` | `end` (default) or `middle`. |
| `overflow`, `max_lines` | `ellipsis` (default) or `wrap`; wrapped visual lines are bounded to 1–8, default 2, with a final end ellipsis. |
| `priority` | 1–100; higher values hide first when minimum widths cannot fit. Without it, a track never hides through priority selection. |
| `from`, `format` | Bind a column to a supported public source (listed below); format as `text` (default), `tokens`, `age` or `count`. Squad-owned fields cannot be bound. |
Supported `from` paths are `member`, `presence`, `cwd`, `target`,
`session.driver`, `session.model`, `session.usage.tokens`,
`session.usage.remaining`, `meta.<key>`, `meta.squad.<field>` and
`fields.<configured-provider>`. Without `from`, a format reads the column's
squad field; `member`, `role`, `state`, `pending` and `note` cannot use either.
On the board, `%` is a share of the **whole content width** after borders and
row marks, before fixed columns are deducted; gaps are additional, as in CSS.
Covered percentages total at most 100%. A configured width is clamped to the
cell bounds. Without a width, the base is `min`, defaulting to four cells for a
growing track and natural content otherwise. `grow` without `max` shares the
remainder as CSS `fr`; capped growing tracks reach `max` before fr tracks share
what remains. Priority hides optional tracks whole before sizing when their
minimums cannot fit. Non-priority overflow keeps earlier sizes; a right cut
needs four visible cells, otherwise that cell hides whole. CLI lists retain
their after-gap percentage base, bounds, weighted growth and scalar fitter.
The board and `tmt ops sq ls` can therefore differ by a cell or two in a column
with a percentage width.
A column's own width/min/max/grow apply only if some line covers its positional
track. Empty cells and spans count as coverage. Uncovered trailing columns
are value-only: their fields can appear on another track without reserving
an extra column. `ls --json` adds **`valueOnly: true`** only to these column
entries; ordinary columns omit the key. Their source/format metadata and full
row values remain available. Text `ls` lists their values naturally and ignores
their width/min/max/grow settings.
This checkout example splits the remainder with task/PR weights **62:26**;
`ctx` and `model` supply footer-line values on existing tracks, not extra widths:
```toml
[squad.checkout.rows]
columns = [
{ name = "member", width = 30, truncate = "middle" },
{ name = "state", width = 10, overflow = "wrap", max_lines = 4 },
{ name = "task", grow = 62, min = 20, title = "WORK", overflow = "wrap", max_lines = 3 },
{ name = "pr_state", grow = 26, min = 10, title = "PR", priority = 2 },
{ name = "ctx", from = "session.usage.tokens", format = "tokens", width = 6, title = "" },
{ name = "model", from = "session.model", width = 14, title = "" },
]
lines = [
["member", "state", "task", "pr_state"],
["", { field = "pending", span = 3 }],
["", { field = "ctx" }, { field = "model", span = 2 }],
]
```
## When a rule is unclear, ask
Don't guess, and don't invent conventions. Ask the user and record what you
agree on. Examples:
- which states to use and what each one means;
- what counts as pending;
- who writes which fields;
- how members are started (worktrees, windows, sessions).
Record the agreement in your notes (`tmt notes path` prints your notebook's
path), or propose an `ops.toml` change for the user to apply. Squad never
starts members, worktrees or windows; that is yours to arrange with the user.
## Team workflow and previous board view
- Squads with no layout key use the team workflow unless they set the simple board
- form, which keeps crew. Explicit `crew`, `pr-queue` and `minimal` retain their
- workflow settings. Presentation defaults to the boxed `members` view independently.
- Choose the named `team` view to restore rows beside a right column (62/38), with
- detail above replies (50/50) in the top 60% and lead notes in the bottom 40%.
-
- Below 100 columns of board body width, the team view folds detail and replies into title
- bars: `board.fold_below = { width = 100, panes = ["detail", "replies"] }`.
- `d` toggles detail and replies together; click either title to toggle it alone.
- Widening restores automatically folded panes without moving focus; manual folds
- keep the session policy described above. Custom split
- boards can set `fold_below` with width 1–1000 and panes present in their layout.
-
- Member, state and PR use percentage widths (22%, 14%, 24%); TASK grows from
- a 20-cell minimum and MODEL follows its content up to 8 cells. Usage columns
- follow the sampling and active-window policy under Observed token usage above.
- Values truncate with the existing ellipsis.
-
- Team uses crew states and pending-first ordering. Rows show member, state,
- task, PR and model (`session.model` from the existing presence read); pending
- text has its own line under task, styled with `token = "waiting"`. Other
- presets keep their styles unless their cells opt in. Its `pr` field uses `preset = "github-pr"`
- from `pr_link`, refreshed at most every 60 seconds per member. A missing link
- never runs `gh`; unavailable or failed provider results follow the normal
- missing/`?` rules. A `rows` or legacy `columns` table replaces the whole grid;
- `fields.<name>` replaces that provider's whole table, other provider names add
- to `pr`, and reminder keys override individually. Set a full `board.layout`
- or `board.panes` to replace the nested pane arrangement; `direction` or `sizes`
- alone is refused for nested/named arrangements. Theme selection is unchanged.
-
- Team enables observed age at 30 minutes. Other layouts keep it disabled by
- default; `[squad.<name>.reminders] enabled = false` disables it for team too.
+ Workflow presets remain independent of the new shell. Squads with no layout key
+ use team unless their simple board form keeps crew; explicit crew, pr-queue and
+ minimal keep their workflow settings. Team uses crew states, pending-first order,
+ the existing task/PR/model projections and a 30-minute observed-age default.
+ A missing PR link never invokes `gh`; unavailable or failed provider values keep
+ the normal missing/unknown distinction. Authored row/provider tables and reminder
+ overrides retain their validation. They do not revive a factory board view.
## Optional observed age
The user can configure observation per squad; the threshold defaults to 30
minutes. Team enables it by default; the other layouts disable it:
```toml
[squad.product.reminders]
enabled = true
stale_after = "30m"
```
The threshold accepts whole `s`/`m`/`h` durations from 1 minute to 24 hours.
The first observation starts a grace period; existing work is never backdated.
Missing or unreadable notes, unavailable cache and rollback clocks are unknown.
Cache loss/corruption starts a new period; config edits do not reset age.
Disabling stops observation; after re-enabling, surviving fingerprint matches
keep their first-observed time. Disabled observation does no cache work and
- never creates a notebook. The board dims a stale row and shows its age at the
- row's end, and puts the notes' age on the notes pane title; the leads tab
- shows no ages. Home shows blocked ages only
- where this observation policy is enabled (Team by default; other layouts off);
- disabled or unavailable observation provides no age. Request ages use the real
- inbox timestamp, and pending-only rows have no age. Home labels blocked
- age `observed`: unchanged task/state observation, not an authoritative blocked start.
+ never creates a notebook. Observation belongs to the unchanged task/state fingerprint, not an authoritative
+ blocked-start time. Disabled or unavailable evidence supplies no age. Request
+ ages use the real inbox timestamp; pending-only rows have no request age. The
+ workspace retains these data distinctions without the former Notes-pane or
+ lead/HOME-tab placement.
The row's age changes only when its raw task/state changes; links, notes and
provider refreshes do not renew it. `activityAfterUpdate` records relevant
observed PR link changes, successful current `github-pr` state transitions to
open/merged, a submitted member final, or an authoritative idle transition in
public `session.activity` after the task/state update. Ordinary `ls` and board
reads retain that idle evidence; the reminder never probes live presence or
uses self-reported activity. Cold provider data and bounded room history can
miss transitions. Idle is never guessed from silence or offline presence.
With Ops extension hooks enabled (`tmt extension hooks enable ops`) and
the provider hook installed through consented `tmt setup`, Squad may add one
informational line to the lead's next turn, including SessionStart context.
It never emits at Stop. Enabling reminder settings installs no hook. Start
observations with `tmt ops sq ls` or the board: a cold cache stays silent. Disabled,
fresh, already-claimed and non-lead cache checks call no core and take no room
lock; warm candidates revalidate the current config, room and sole lead. The
best-effort preflight examines at most 128 cache-directory entries per call.
A reminder names stale notes or counts/names stale rows with relevant activity.
The line is sanitized and at most 240 characters; one invocation has an aggregate
300 ms budget including child cleanup, capped by the host's earlier deadline.
No provider or network runs in the hook. The host isolates the hook's process
group, and nested public reads remain in it so timeout cleanup reaches them.
Context calls require an isolated process group owned by the extension.
One claim bit per content generation is atomically published before handoff.
Concurrent calls share the nonblocking room lock. A lost handoff, crash or host
cutoff after publication can lose a reminder; it is never blindly retried.
At-most-once applies while the cache survives: loss/corruption restarts grace,
and changed content starts a new generation. This is best-effort context, not a
notification queue. Board reminder-setting controls are a separate slice.
## Offline markup authoring
`tmt ops sq layout validate board.xml` checks an authoring file only; add `--json`
for a structured result. Success exits 0, and invalid/unreadable files and invalid
command syntax exit 1. It does not discover core, read Squad config, run
- providers or open a terminal. **The board does not load these files.** Board rows
- and pane composition still come from the existing Squad settings.
+ providers or open a terminal. **The board does not load these files.** The
+ interactive tree and conversation use workspace preferences; authored row and
+ factory values remain independent CLI projections.
The `squad-projected-v1` schema declares `$.squad.name` and `$.rows` (a collection).
With `each="$.rows" as="row"`, each row declares stable `row.id`, scalar `name`,
`presence`, `pending`, and `fields`/`colors` with valid Squad row field names.
Field names and sources reuse the row/config owners; IDs must bind stable IDs.
Binding checks repeat bodies even without data. Sources reuse column `from`/`format`
in lexical `row` scope. Provider field names are checked syntactically offline;
provider configuration/data availability is not checked. Direct binds retain display text.
```xml
<tmt-view version="1" class="flex-col">
<tmt-repeat each="$.rows" as="row">
<tmt-row id-bind="row.id" row-bind="row.id" class="grid grid-cols-[12_1fr] gap-1">
<tmt-cell bind="row.name"/>
<tmt-cell bind="row.fields.task" wrap="true"/>
</tmt-row>
</tmt-repeat>
</tmt-view>
```
Width steps are markup, not class prefixes. `<tmt-switch>` holds ordered
`<tmt-case min="lg">` / `<tmt-case min="md">` branches and a final `<tmt-default>`;
exactly one branch is laid out, the first whose `min` fits the container's width
(`of="terminal"` measures the whole terminal). `min` and `hide-below` take a step
name (`sm` 80, `md` 100, `lg` 140 cells), never a number, and a width exactly at a
step takes it. `hide-below="md"` on a row, column, cell or text is the one-element
form. Every branch is checked offline, so an error shows at every width.
```xml
<tmt-view version="1" class="flex-col">
<tmt-repeat each="$.rows" as="row">
<tmt-row id-bind="row.id" row-bind="row.id" class="flex-row gap-1">
<tmt-cell bind="row.name"/>
<tmt-switch>
<tmt-case min="lg"><tmt-cell bind="row.fields.task" wrap="true"/></tmt-case>
<tmt-default><tmt-cell bind="row.fields.task" class="truncate"/></tmt-default>
</tmt-switch>
<tmt-cell bind="row.presence" hide-below="md"/>
</tmt-row>
</tmt-repeat>
</tmt-view>
```
Integers are terminal cells: `w-4` means 4 cells, unlike Tailwind's rem scale.
Use `flex`/`flex-col`, `grid`, `grid-cols-[12_30%_1fr]`,
`grid-cols-[minmax(4,1fr)_8]`, `col-span-N`, `gap-N`, cell padding and bounds.
A cut column keeps at least four cells or hides whole; Squad chooses priority tracks before sizing.
Admission bounds: 256 KiB XML, depth 32, 20,000 parser/expanded nodes, 20,000
repeat iterations and 8 MiB bound text/ID bytes. The offline check validates the
template; runtime expansion limits require data and are not simulated here.