sase's TUI User Guide¶
Overview¶
sase's TUI is the primary TUI for the SASE toolkit. It provides an interactive interface for navigating, managing, and operating on Patches, agents, and machine services.
Launching¶
sase tui [QUERY] [options]
Run sase tui from an interactive terminal. It opens on the Agents tab and starts the
service host (pass -x to skip that). Press ? on any tab for the keymap and a short
guide, : for the Command Line, ; for the
Command Palette, and q to quit. The command was previously
sase ace, which no longer exists; the TUI's settings still live under the ace:
section of sase.yml (for example ace.keymaps and ace.page_size).
If no Patches query is provided, sase's TUI loads the last used Patches query, then the
first saved Patches query, then falls back to !!! for error suffixes. The top-level
Agents tab restores its own last submitted Agents query after startup.
CLI Options¶
| Option | Description |
|---|---|
QUERY (positional) |
Query string for filtering Patches |
-m, --model-tier |
Override model tier for all LLM providers (large or small) |
-M, --model-size |
Deprecated alias for --model-tier (big or little) |
-p, --profile [PATH] |
Profile the TUI session with pyinstrument; optional output path |
-r, --refresh-interval |
Auto-refresh interval in seconds (default: 10, 0 to disable) |
-s, --sanity-refresh-interval |
Full sanity-refresh interval in seconds (default: 300); see Auto-Refresh |
-x, --no-service, --no-axe |
Disable auto-starting the service host on startup |
-v, --vcs-provider |
Override VCS provider (git, hg, or auto) |
-R, --restart-service, --restart-axe |
Restart an already-running service host on startup (no-op if it is not running) |
-t, --tab |
Tab to focus on startup (agents by default, artifacts, or services; axe is a compatibility alias, and changespecs and patches alias artifacts) |
-T, --tmux |
Launch sase's TUI in a new tmux window and print the target for external control |
When profiling is enabled, sase's TUI writes text output to PATH. If PATH is
omitted, it uses the managed temp tree:
$SASE_TMPDIR/ace-profiles/ace_profile_<timestamp>.txt when SASE_TMPDIR is set, or
$SASE_HOME/tmp/ace-profiles/ace_profile_<timestamp>.txt otherwise (SASE_HOME
defaults to ~/.sase). On exit, sase's TUI prints the shortened path and copies it when
a clipboard tool is available.
Examples¶
sase tui # Last query, first saved query, or "!!!"
sase tui '"feature" AND "Drafted"' # Filter by name and status
sase tui '+myproject' # Filter by project
sase tui -m small -r 30 '!!! OR @@@' # Small model, 30s refresh
When --profile is enabled, sase's TUI prints a shortened profile-output path after the
TUI exits and tries to copy that shortened path to the system clipboard (pbcopy,
wl-copy, xclip, or xsel when available).
Agent Screenshots¶
Use sase screenshot when you need a PNG of the real TUI state an agent would see. The
command starts sase tui in the detached sase_ace_agents tmux session at 120x40,
waits for the screen to paint, sends optional tmux keys, asks the live app to export
SVG, then rasterizes that SVG with the same bundled-font renderer used by the golden PNG
suite.
sase screenshot -o /tmp/sase.png
sase screenshot -p j -p j -w 'Ready' -o /tmp/sase.png
sase screenshot -p / -w INSERT --type 'machine:apollo' -w 'machine:apollo' -p enter -o /tmp/sase.png
sase screenshot --host apollo -o /tmp/sase-remote.png
sase screenshot --keep -- -t services
sase screenshot --window sase_ace_agents:sase_tmux_1 -o /tmp/sase-again.png
Repeatable -p/--press, -T/--type, and -w/--wait-for form one argv-ordered input
script, so a query filter can be typed and submitted without a second --window
capture. --type sends literal TUI text (tmux send-keys -l). Send the query key as a
literal -p /: tmux has no slash key name, so -p slash types the letters slash.
Wait for the input to open (-w INSERT) before typing, and wait for the typed text
before -p enter.
By default, the tmux window is killed after capture. Pass --keep to leave it running,
drive it manually with tmux send-keys, and recapture it later with --window. The
command pins TUI animations off and uses truecolor terminal defaults, but it still shows
live machine state and timestamps by design. If capture times out, the error includes
the final tmux capture-pane text so you can see what the TUI was showing.
Pass --host to run the live TUI and SVG export on an enrolled machine alias or any raw
SSH destination, then fetch the SVG and rasterize it locally. Enrollment only supplies a
convenient ssh_target; SSH itself is full shell access and uses your normal SSH trust
boundary, not gateway-scoped credentials. Remote screenshots show that machine's
installed sase and state, not your local working tree. The remote sase commands run
through the account's noninteractive login shell so tools installed under login-profile
paths such as ~/.local/bin can be found without allocating a terminal. Check the
printed remote_sase_version=sase <version> line when comparing results; it is read
from sase version --json on the remote host. A remote host needs sase, tmux, and
SSH access, but it does not need the local visual rasterizer dependencies.
Clipboard Transports¶
Every copy inside sase's TUI runs in the background and tries both a verifiable system
transport and OSC 52 for the client terminal. Inside tmux, sase's TUI tries
tmux load-buffer -w - first; otherwise the system candidates are pbcopy, wl-copy,
xclip, and xsel as appropriate for the platform and display environment. A plain
Copied … toast means a subprocess transport confirmed success, while
Copied … (OSC 52) means sase's TUI emitted the terminal escape sequence without a
verifiable subprocess result. OSC 52 payloads above the terminal-safe size limit are
skipped. If neither transport works, sase's TUI opens the generated text in a read-only
fallback view so it can still be selected and recovered.
Tab System¶
sase's TUI has three tabs, cycled with Tab and Shift+Tab:
While a prompt input bar is open, Tab/Shift+Tab belong to the prompt (snippet
expansion, tabstops, list indent) and never switch tabs.
| Tab | Description |
|---|---|
| Agents | View running and completed agents, their files and prompts |
| Artifacts | Browse the durable Agent catalog, Stitches, Patches, Beads, configured document providers, and Files. See the Artifacts pane contract and visual grammar. |
| Services | Monitor the service host, configured service procs, scheduler work, and background commands |
Agents is the first tab and the startup default. Each tab has contextual help: press ?
to open the Help modal on its Keymaps view, then ] to switch to the tab's
Guide view. While Help is open, the configured tab-switch keys still switch sase's
TUI tabs and refresh both views in place. By default those keys are Tab and
Shift+Tab; if you remap them, the modal follows the configured keys.
Press / in the Keymaps view to open a live filter bar. Typing splits the query into
whitespace-separated tokens that must all match — each token is checked against a
row's section name, key display, or description, so a token that matches a section name
(e.g. beads) pulls in every keymap in that section. Matched text is highlighted and a
counter shows how many keymaps and sections matched. The filter follows you across
sase's TUI tab switches while Help stays open, but resets whenever the panel is closed
and reopened. Esc clears an active filter before it closes the Help modal.
On first use, empty tabs render onboarding states instead of blank panels: the Patches
view shows a getting-started card when no Patches or saved queries exist yet, and the
Agents tab walks through launching a first agent — the project/Patch launch hint appears
only when a launchable target exists — and can recommend installing plugins from the
Admin Center when no third-party plugins are installed. Onboarding cards carry "learn
more" links into the published docs. An empty Beads pane points agents to
/sase_new_task, calls out sized draft tasks, and explains how ready tasks enter
TaskTriage.
Within Artifacts, number keys follow the visible left-to-right order of the strip:
Agent, Stitch, Patch, and Bead are always 1, 2, 3, 4; configured
document-provider tabs such as Plan and Research take the next digits; and File, which
always renders last, carries the highest digit — 5 with no provider tabs configured,
7 with two. Digits stop at 9, and File keeps 9 when the runtime strip grows past
nine panes; middle overflow panes may then have no digit. Use [ / ] to cycle through
the complete runtime strip. As horizontal space tightens, the strip chooses the widest
tier that fits from a full → compact → micro ladder and re-renders only when the tier
changes. Compact removes outer padding; micro also tightens the separators and hides
inactive labels, leaving their digits and icons visible. If even micro is wider than the
available space, micro remains selected. Every Artifacts tab has an icon, including
provider tabs whose missing or invalid ref.icon falls back to the generic ◆, so the
micro tier never leaves an inactive tab unidentified. Hovering a tab shows that pane's
one-sentence description. These keys act only while Artifacts is visible. Press p on
any Artifacts pane to change the shared project scope — first-open seeds from the
current project — or use the command palette to jump directly to a
top-level view. Patches remains query-scoped: choosing a project there rewrites the
Patches query's project: token. The strip order does not change the entry point:
opening Artifacts still selects Stitch by default.
Contextual Artifact Links¶
sase's TUI owns one link rail across the three top-level tabs. It appears when the current selection has artifact links: an Artifacts-pane entry, a named agent or session turn, or an AXE job. Synthetic clan containers, routines, and background-command rows do not provide a link subject. The rail includes a breadcrumb while a link-follow trail is active.
Press $ to arm the rail. Then press $ again to follow its first entry, 1-9 for a
numbered entry, or 0 to open the complete links panel. The rail always advertises
$0 all or $0 +N more, so links beyond the first nine remain discoverable. Several
projected links produced by the same rule may collapse into one entry; following that
entry opens the panel already scoped to the group.
In the panel, a-z follow the first 26 entries directly, Enter follows the
highlighted entry, arrows or Ctrl+N/Ctrl+P move, and Esc closes it. - removes a
writable durable link and + creates a link from marked rows where that action is
available. Projected links are read-only. Rows show relation direction, rationale,
provenance, and missing targets; the panel shows a separate notice when the aggregate is
stale. Two glyphs pre-flag a row before you follow it, using the same resolution the
follow path itself uses: ⊘ means the ref is dangling — no pane resolves it at all —
and ↻ means the target exists but is not selectable in place, so following it will
rewrite the destination pane's query. A destination pane that is still loading is not
flagged, because "not in the result set yet" there is a loading artifact rather than a
real reveal need. The ? help modal lists both glyphs beside the link-follow keys.
Link follows can cross tabs and keep a bounded 32-hop trail. Ctrl+O walks backward and
Ctrl+Shift+O walks forward, restoring the tab, pane, project scope, query, selection,
and supported AXE fold state. If no link-trail hop is available, those keys retain the
current pane's normal jump-stack behavior. Ordinary navigation clears the link trail.
See Artifact Links for relation and
projection details.
Link Jumps¶
Following a link whose target is outside the destination pane's current result set does
not fail. Every jump — the $ link rail, the $0 links panel, and relation jumps
outside Patches — runs one engine: it plans a single verified rewrite, then commits it
once. The query bar always truthfully describes what is shown, and query history records
exactly one ^ entry, which restores the original query and selection. While the
destination pane is still loading, the jump waits for it rather than reporting a miss.
Starting a new jump cancels one still in flight.
| Step | When | What happens |
|---|---|---|
| Resolve | Always | Find the target through the destination pane's own row identity, re-resolved once the pane has loaded. |
| Scope | The current project scope excludes the target | Switch to the target's project, or to All projects if its project is unknown and the current scope cannot resolve it. Never narrows from All. |
| Select | The target is visible | Select it. No toast. |
| Fold | A collapsed fold hides it | Expand the minimum fold, then select. No query change, no toast. |
| Acquire | The row is not in the pane's loaded inventory | Fetch it directly from its source in the background, then continue. |
| Context | The query or limit hides it | Rewrite to the context query below, verified against the target's row before committing. |
| Identity | No context, or the context still misses | Rewrite to the tightest query that names the row (id:, sha:, …), keeping the limit. |
| Neutral | Still missing, where allowed (never Stitches) | Replace the query with a blunt limit:all. |
| Honest report | Everything missed | Say so: a dangling ref, a load failure, an unconfigured provider pane, or "not in <Pane>". |
The rewrite lands the target inside its natural session — never an isolated row — and
keeps the current limit:, raising it only when the session would not fit:
| Target | The query becomes | Lens label |
|---|---|---|
Bead phase sase-16n.7 |
id:sase-16n.* |
epic sase-16n |
Bead epic sase-16n |
id:sase-16n id:sase-16n.*, epic fold expands |
epic sase-16n |
Bead task or flag sase-abc |
id:sase-abc |
bead sase-abc |
Agent hood member sase-16n.7 |
name:sase-16n.*, or (name:sase-16n OR name:sase-16n.*) when the root exists |
sase-16n hood |
Lone agent x |
name:x |
agent x |
Agent session turn x--y |
session:x |
session x |
| File | agent:<creating agent>, else id:<file> |
files from <agent>, else file <id> |
| Plan or provider doc | path:<doc path> |
plan <name> or <provider> <name> |
Stitch repo@sha |
repo:<repo> since:<day> until:<day>, plus project:, sidecar:true, or merges:show as needed |
<repo> · <day> |
| Patch in a stack | ancestor:<stack root> |
stack <root> |
| Submitted, reverted, archived patch | name:<patch> |
patch <name> |
The * above is the wildcard filter: id:sase-16n.*
matches every phase of epic sase-16n but not the epic itself.
An agent: link lands on the Agents tab when the agent is loaded there, opening any
collapsed fold, group banner, or tribe panel around it. The Agents-tab main filter is
never rewritten: an agent it hides lands in Artifacts ▸ Agent instead. A job: link
lands on the Services tab and expands the job's routine fold.
Each rewrite explains itself in one toast naming the new query, what hid the target, any
scope change, and the real keys that restore (^) and walk back (Ctrl+O):
↪ Bead sase-16n.7
query id:sase-16n.* limit:100
was -status:closed limit:100 · hidden by -status:closed
epic sase-16n · ^ restore · Ctrl+O back
Extra lines appear only when they apply: scope <old> → <new> (project display names,
or All projects), fetched outside the loaded rows after an Acquire, and
Agents tab filter hides it — showing Artifacts ▸ Agent. The was line names the terms
that hid the target (hidden by your query in a Boolean-dialect pane, or
hidden by the project scope), or past limit:N when only the limit did. The key names
follow the live keymap, and the toast stays for about 8 seconds. Selecting,
fold-expanding, switching scope, or fetching without a rewrite shows no toast.
While a rewrite is live, the pane's info header shows a reversible lens chip —
↩ sase-16n.7 · epic sase-16n in the pane's accent color, followed by a dim
^ to return naming the configured prev_query key rather than introducing a new
binding. The lens is derived from the live query rather than stored as a flag: it is
active only while the pane's canonical query is still exactly the query the reveal wrote
and the pane's dialect has not changed. Editing the query yourself, walking query
history with ^ / _, loading a saved query, or triggering a fresh reveal all end it
with no separate clear step.
Split Modes in Artifacts Panes¶
Every Artifacts pane starts with an even left-list/right-detail split. Press } to grow
the list panel or { to shrink it. The mode is shared by every Artifacts sub-tab and
cycles with wraparound in either direction:
| Mode | Left panel | Right panel |
|---|---|---|
narrow |
30% | 70% |
even |
50% | 50% |
wide |
70% | 30% |
The {████} badge at the right of the sub-tab strip shows the current mode in the
active pane's accent color: one filled cell is narrow, two is even, and three is wide.
Clicking the badge cycles forward, like }.
The Patch pane remains content-sized instead of reserving empty list space. Its mode sets the maximum list width for the available terminal width, while the existing 43-cell readability floor and 80-cell upper bound still apply.
Pane Description Brief¶
Every Artifacts pane — the five built-ins, the Plan pane, and every pane a sidecar ref
creates — carries a resolved, never-empty description, shown as a host-owned brief
directly under the sub-tab strip. Press D to cycle it through three modes:
| Mode | What you see |
|---|---|
off |
Nothing; the row disappears entirely. |
summary |
One line: an icon and a one-sentence summary, ellipsized to fit. |
full |
The summary plus a longer body, capped at 6 rows. |
ace.artifacts.description_mode seeds which mode a session starts in (default
summary); D changes it in memory for the current session only. Clicking the brief
cycles it the same way. An unconfigured document-provider pane shows, in full mode
only, a dim italic hint naming both places its description can be configured:
ref.pane.description in the sidecar's ref config, or
ace.artifacts.panes."<pane_id>".description in sase.yml. See
ace.artifacts for the config surface and
Artifacts pane visual grammar for the
full rendering rules.
Navigation in Agent, Stitches, Beads, Provider Documents, and Files¶
The non-Patches panes share fast navigation over their selectable left-panel entries. Stitches and Files skip day headings; Agent, Beads, and provider document panes skip group, section, and empty-state rows as applicable. Movement clamps at the first or last entry and silently does nothing when a list is empty.
| Key | Action |
|---|---|
g / G |
Select the first / last agent, commit, issue, bead, document, or file |
Enter |
Open the selected commit, document, bead, or file in its full-screen reader |
Ctrl+F / Ctrl+B |
Move down / up 10 selectable entries |
Ctrl+D / Ctrl+U |
Scroll the active right-hand detail pane down / up (half page) |
' |
Show adaptive entry hints; press ' again for the first entry or the last jump origin |
Ctrl+O / Ctrl+Shift+O |
Walk the link trail first, then the pane jump stack; back falls through to first hint |
$$ / $1-$9 / $0 |
Follow the first / numbered artifact link, or open the complete links panel |
Ctrl+J / Ctrl+K |
Load ace.page_size more rows / unload that page (rewrites the host limit: cap) |
Hint keys select an entry without activating it. Jump-back history is kept separately for each non-Patches pane, and stale origins disappear automatically after filtering, changing project scope, refreshing data, or collapsing an expanded bead tree. Escape or an invalid hint exits jump mode. These actions use the configured keymap values; the keys above are the defaults.
The Agent pane is the deliberate exception to Enter: its detail is already inline, so
selecting a row updates the right-hand identity, lifecycle, lineage, provenance, prompt,
chat, and published-page view without opening a reader.
When the selected entry has declared relationships, a relation panel appears at the
bottom of the list column, starting collapsed into a one-line rail that names its own
expand key (. by default) so it is never hidden knowledge. Press .
(toggle_relation_panel) or click the rail to expand it; the expanded panel's border
carries the reverse affordance ({key} collapse, bottom-right) so it can be collapsed
the same way. Its section names come from the pane contract, so examples include parents
and children, document lifecycle stages, dependencies, linked beads or plans, and
file-version families. Navigation is two keystrokes: first a relation mode, then the key
printed in square brackets beside the target row. The modes are < for ancestors, >
for descendants, and ~ for session or siblings, and the footer lists only the ones the
current entry actually has; these keys stay live even while the panel is collapsed. A
section header ending in (N hidden) means the query is filtering out that many
targets, a row ending in (missing) points at an entry that no longer exists, and a row
ending in → <pane> crosses to another Artifacts pane.
On Patches, choosing a hidden same-pane target rewrites the query to reveal it rather
than failing. Patches saves the query and selection you started from first and pushes
the old query onto the same history stack ^ walks, so ^ returns to the exact view
you came from. Other panes run relation jumps through the same engine as $ link
follows (see Link Jumps): a filtered-out target is unfolded, fetched, or
revealed by one reversible query rewrite, and each jump adds a link-trail hop that
Ctrl+O walks back.
Shared entry-jump surfaces allocate hints from the zero-based alphabet 0–9, a–z,
A–Z. A session with at most 62 targets uses one character (0 through Z). A
larger session uses two characters for every target, beginning 00, 01, …, 0Z, 10
and ending at the fixed ZZ capacity. The first character of a two-character hint keeps
jump mode open; the second completes the jump. Hints remain case-sensitive.
Outside the Artifacts panes above, a single shared implementation backs ' everywhere
it appears: each Admin Center working section (see
Global Keybindings) and four modals — the notification options
modal, the model picker, the saved-group revival modal, and
Launch Control. In all of them, pressing ' a second time while the
hints are painted is the jump back key: it pops the most recent origin off a bounded
stack of the last ten pre-jump positions, rather than toggling between one saved target
and the current row. With an empty stack it falls through to the first hinted row
instead. The footer shows which of the two the next ' will do — JUMP ' back while
the stack holds an origin, JUMP ' first when it does not. Changes that shift which row
is where — refiltering the model picker, paging or deleting in the revival modal,
drilling into or out of a Launch Control bucket, or an async provider-snapshot reload —
discard the stored origins instead of leaving them pointing at whatever row inherited
the index.
Copy Mode in Agent, Stitches, Beads, Provider Documents, and Files¶
Press % on any non-Patches Artifacts pane to open the context-aware Copy as…
palette for the visible entry. Rows are grouped by representation, show their configured
accelerator and a warm preview, and can be selected with the mouse, arrow keys or
j/k plus Enter, or the accelerator directly. q/Esc cancels. If an accelerator
is configured as j, k, or q, the configured copy target wins over navigation or
cancellation.
| Pane | Keys |
|---|---|
| Agent | %@ artifact ref · %! ref in agent prompt · %n name · %l Markdown link · %p artifacts path · %c chat path · %P prompt · %j metadata JSON |
| Stitches | %@ artifact ref · %l Markdown link · %J metadata JSON · %! ref in agent prompt · %% full SHA · %m message · %r repo@sha · %p plan |
| Beads | %@ artifact ref · %l Markdown link · %J metadata JSON · %! ref in agent prompt · %% id · %t title · %b description and notes · %d design · %u linked issue ref |
| Plans | %@ artifact ref · %d bead design ref · %l Markdown link · %J metadata JSON · %! ref in agent prompt · %% bead id · %p path · %t title · %b body |
| Other providers | %@ artifact ref · %l Markdown link · %J metadata JSON · %! ref in agent prompt · %p path · %t title · %b body |
| Files | %% contents · %@ artifact ref · %L Markdown link · %p stored path · %o source path · %l label · %j metadata JSON · %! ref in agent prompt |
%s captures the current sase tui tmux pane on every view.
The palette and copied value follow the active pane. After selection or cancellation, the footer returns to the active pane's normal bindings. An unknown printable key warns but leaves the palette open so another choice can be made.
When entries are marked, the reference choice copies newline-separated prompt references, the Markdown-link choice copies a bullet list, the metadata-JSON choice copies one array, and the handoff choice seeds one prompt with the marked set. Entries that cannot produce the selected representation are skipped; the completion toast reports both the copied and skipped counts. The palette title reports the visible marked count and applicable rows use plural labels such as commit SHAs.
The same % prefix opens the palette above copy-forwarding readers and modals,
including the preview panel. Dismissing the palette returns to the underlying modal.
Snapshot choices dismiss the palette before capture, so the palette itself is not
included in the copied pane.
Marks in Agent, Stitches, Beads, Provider Documents, and Files¶
Press m to mark or unmark the selected entry and u to clear the active pane's marks.
Each non-Patches pane keeps an independent stable-target mark set, so marks survive
refreshes and switching panes without affecting marked PRs. Changing the shared
Artifacts project scope clears the non-Patches marks.
When marks exist, the pane's % copy targets operate on marked entries in visible order
instead of only the selected entry. Identity, location, and data representations use
paste-ready forms; content dumps retain labeled fenced sections. Content-shaped targets
are capped at 512,000 bytes per item and per assembled payload, with an explicit
truncation banner and toast. The footer shows the active pane's mark count only while
that count is nonzero.
Filtering Agent, Patches, Stitches, Beads, and Plans¶
Patches keeps its canonical query visible in a persistent, read-only filter row at the
top of the detail column, so the active query is legible without opening anything. Press
/ (or the local f) to start editing it. Typing re-filters the already-loaded Patch
snapshot on every keystroke, so previews are instant and never re-query the store.
Enter commits: it reloads Patches from the committed query, pushes the query you
replaced onto the ^ history stack, and returns focus to the list. Escape abandons
the edit, restoring the committed query, its result, and the row you had selected. A
parse error stays inline in the row and leaves the visible Patch list on the last valid
query. Tab accepts completions for keys, values, shorthand sigils, state predicates,
status shorthands, and — while the row is empty — saved-slot commands. Saved-query
commands such as #3 status:Draft, # status:Ready, and #3 save, allocate, or delete
a slot without changing the active query or closing the editor.
Stitches keeps its effective canonical query visible above the timeline. Press / or
the local f shortcut to focus that row for live editing; Enter commits the query and
returns focus to the timeline, while Escape restores the last committed query and
result. The row remains visible in either case. Beads, Plans, Files, and every document
provider pane keep the same persistent idle row, so the committed query stays visible
without opening an editor. The Beads default is -status:closed limit:<page_size>; that
query lives in the idle bar, not on the counts line. Tokens from different facets
combine with AND semantics; comma-separated and repeated values within one facet combine
with OR semantics. Free-text terms must all match. Press Tab to accept the highlighted
key or value completion.
Agent uses the same persistent query row and the Boolean profile dialect shared with
sase agent search. It accepts AND, OR, NOT, and
parentheses, plus free text over agent names and metadata. Its fields cover identity and
lineage (name, kind, session, clan, tribe, role, workflow, parent,
project), execution (state, status, provider, model, attempt), lifecycle
booleans (hidden, dismissed, revivable, attention, retry), archive links
(linked, relation, artifact), start/finish bounds (since, until, after,
before), and runtime bounds (min, max). Date bounds accept Nh / Nd / Nw /
Nm, today, and YYYY-MM-DD, where Nm means months; runtime bounds accept seconds
or Ns / Nm / Nh / Nd, where Nm means minutes. There are no sigils or macros.
For example, state:dismissed AND revivable:true,
provider:codex AND status:FAILED AND since:7d, and linked:true AND relation:read are
valid queries.
The top-level Agents tab uses the same Boolean grammar through the live agents-live
profile, but keeps zero idle screen-space cost: when no query is active, no filter row
is visible; when a query is active, the detail column shows the canonical highlighted
query and match count. Press / or f to open the auto-hiding filter bar. Live Agent
fields include the shared identity and runtime fields (name, session, clan,
project, kind, role, workflow, model, provider, status, attempt,
hidden, attention, retry, since, until, after, before, min, max,
text) plus operational fields (cl, machine, tribe, pinned, unread, needs,
source). Archive-only catalog fields such as state, dismissed, revivable,
linked, relation, artifact, and label are not live-tab filters, and the live tab
does not accept an Artifacts limit: token.
The Agent catalog also derives three archive capabilities that describe what a dismissed row's persisted archive can actually support, rather than whether a bundle file merely exists:
| Field | True when |
|---|---|
historically_viewable |
The archive holds enough data to inspect the run after the fact. |
durably_revivable |
The archive holds enough data to restore the run. |
restartable |
The archive holds the prompt and model parameters needed to run it again. |
The query schema currently advertises those three field names, but the Agent query index
does not populate their values. Consequently, both historically_viewable:true and
historically_viewable:false (and the equivalent queries for the other two fields)
return no rows. Inspect the revive modal for these per-row capabilities; use
revivable:true when the goal is to find rows sase's TUI can restore.
revivable:true now means all of "dismissed", "backed by a readable top-level bundle",
and durably_revivable, so the saved query the w action seeds
(state:dismissed AND revivable:true) no longer lists rows whose archive cannot
actually be restored. The revive modal shows the three capabilities as Viewable,
Revivable, and Restart lines, plus a Missing line naming any unmet
requirement, and reviving a row that lacks one is refused up front with
This archive record is not revivable: missing … instead of failing at the last step.
Stitches accepts singular project: plus repo:, author:, origin:, type:,
sha:, since:, until:, sidecar:, merges:, and limit: and free text matched
against the commit subject. sha: is the pane's identity field: it is repeatable,
comma-listable, and negatable, and it matches a prefix of the full commit id, so
sha:a1b2c3 selects that commit the way a git short SHA does. origin: accepts
stitch, auto, and manual, and is repeatable, comma-listable, and negatable like
repo: and author:. type: is also repeatable, comma-listable, and negatable; it
accepts manual, automatic (auto alias), stitch, merge, patch, and observed
SASE_TYPE footer values. merges:hide/show/only controls merge-commit visibility
exactly like sase stitch list's --merges flag (see
VCS Provider Reference). project: is not repeatable,
comma-listable, or negatable because it selects the repository constellation before
commits are collected. With no project: token, collection spans all projects. It
accepts a configured project name, ProjectSpec directory key, or alias; committed known
values are rewritten to the configured project name. On first open, when
ace.current_project.seed_filters is on, the shared Artifacts project scope seeds from
the current project unless an explicit project: / +name term
already selected one. The project picker replaces that token while preserving every
other committed token; its All projects choice removes it. The compatibility a
action removes an active project token and restores the last automatic or picked project
on the next press.
The bundled initial query is sidecar:false merges:hide since:24h; sase's TUI injects
limit:<ace.page_size> (default limit:100) when that string has no limit: token. It
is configurable with ace.artifacts.stitches.default_query
(ace.artifacts.commits.default_query is a deprecated alias), and changes take effect
the next time sase's TUI starts. An explicit project: in sase's TUI query or in the
configured default query wins over the current-project seed. An empty parsed query
includes sidecar repositories; at sase's TUI startup the async Artifacts seed can add a
visible project token. Canonical rendering always includes either sidecar:true or
sidecar:false, and the configured d action rewrites that same visible token.
Selecting a sidecar with repo: therefore requires sidecar:true. For example,
project:sase repo:sase author:Ada origin:stitch since:7d sidecar:false fix shows
recent tracked SASE commits by Ada whose subjects contain fix,
repo:plans sidecar:true shows that sidecar across all projects, and limit:40 caps a
deliberately broad search. Day-granular until: values (today, yesterday, and
YYYY-MM-DD) include the full named day; relative and minute-precise values remain
instant bounds. Relative windows such as since:24h re-anchor whenever the pane
refreshes.
The repository legend starts with [P/N], where P is the selected commit's one-based
position and N is the number of matched entries currently displayed. Day headings do
not count as entries. A + on the denominator, as in [1/40+], means the displayed
total is only a lower bound. The persistent filter row reports the corresponding
coverage state (exact, preview, or capped) without repeating the match count. Each
timeline row also has a fixed origin glyph immediately before the subject: ✦ stitch
for commits created through sase stitch create, ↻ auto for other SASE-created
commits, and ✎ manual when the commit has no SASE provenance footer. The legend lists
only the origins present in the displayed commits, so a result containing only tracked
work shows only ✦ stitch.
Every Artifacts pane accepts a host-owned limit:N token that caps how many matched
rows the list shows. It is not a row property: sase's TUI extracts it before dialect
parse / Rust eval, matches against the remainder, then slices. Startup writes
limit:<ace.page_size> into each pane's default query when no limit: is present; a
user-authored limit:40 or limit:all is left alone, and deleting the token leaves
that pane uncapped. Ctrl+J raises the cap by ace.page_size and Ctrl+K lowers it,
never dropping below one page; unload of an unlimited query (limit:all or a deleted
token) introduces limit:<ace.page_size>. When the cap clips the result, the filter row
says capped and Stitches uses a lower-bound total such as [1/40+]. limit:all is
the unlimited synonym on every pane; limit:0 is also accepted by the shared parser
(Stitches used that spelling first). Canonical query text omits both. Provider or
aggregate truncation metadata can still mark a count as capped without inventing an
active query limit.
Plans accepts kind:, status:, tier:, project:, path:, since:, until:, and
limit: plus free text matched across plan-document metadata and content. path: is
the pane's identity field — the document path or provider identity, matched by substring
so path:<fragment> finds a document by any part of its path, and still searched by
free text — and every other document-provider pane gains the same substring path: key
when its declaration does not already define one. kind: accepts proposal, active,
archive, and the document-sidecar roles present in the current scope, such as plans,
research, or designs. kind:archive matches committed documents that are not linked
from a live bead, while kind:designs narrows documents to that sidecar.
Beads accepts repeatable id:, type:, task_type:, tier:, status:, size:,
due:, project:, assignee:, owner:, model:, has:, bug:, label:, since:,
and until: terms, plus host-owned limit:. id: is the pane's identity field: it
matches a bead id exactly, and bead ids remain free-text searchable as well, so
id:sase-w3.2 pins one row while a bare sase-w3 still matches by substring.
task_type: accepts catalog slugs plus untyped for legacy beads. Status values
include the five stored states plus the derived blocked, launched, and triage
states. due: accepts live, soon, or due and matches
flag beads — the dedicated feature-flag removal tasks —
by how close they are to their removal thresholds. Other bead types have no due state,
so a positive due: term hides them. has: accepts +1, reopened, plan, bug,
deps, notes, and triage. bug: matches issue state, reference, relation, or
project, with completion for none, open, closed, stale, drift, mirrored, and
referenced; label: matches cached provider labels. Free text also searches cached
external issue title, body, URL, and labels alongside the bead id, title, description,
notes, design, references, and ownership metadata.
A leading unquoted - excludes a match. Stitches can exclude repositories, authors,
origins, types, commit SHAs, and subject text. Plans can exclude kind:, status:,
tier:, project:, path:, and free text. Files and Beads can exclude every declared
filter facet and free text, including since: and until:. Exclusion wins when
positive and negative constraints overlap: repo:sase,plans -repo:plans,
author:Ada -author:bot, and status:open -status:blocked are all valid. A comma list
negates the whole token, so -repo:plans,research excludes either repository. Stitches
and Plans do not allow negated date bounds; Files does, but permits each date key only
once. Beads permits repeated and negated date bounds. limit: cannot be negated in any
pane. Stitches' project:, sidecar:, and merges: fields are singular and cannot be
negated; sidecar: accepts only true or false, and canonical queries always render
its explicit value. Quote the whole token to search for a literal leading minus
("-repo:plans"); quote only the excluded value to keep negation active
(-"generated rollout"). Matching remains case-insensitive, and repository/project
aliases work for both inclusion and exclusion.
In the flat-token query grammar, a declared Boolean field also takes a bare shorthand:
an unquoted key token with no colon expands to key:true, and -key to -key:true.
Quoting opts out, so "key" stays free text. Because the shorthand is purely lexical
(it always canonicalizes to the long spelling), it costs no schema field and moves no
compiled profile's digest, so no saved query is invalidated. Stitches' sidecar field
is Boolean, so a bare sidecar filters to sidecar:true instead of searching for the
literal word in a commit subject; quote it ("sidecar") to search for the word. The
Agent pane uses the Boolean-expression grammar instead: write revivable:true there,
because bare revivable remains a free-text term.
Bead Pane¶
The top-level Beads view (4) is the work-item home for standalone tasks, epic plan
beads, and their phase beads. Every bead appears once: tasks occupy their own section,
while epics expand with l and collapse with h to reveal phases. Rows show stored
status and ownership metadata, ✦ when a task has a pending TaskTriage decision, and
▤ when the bead links a plan document. Linked issue chips use ○ for open, ● for
closed, and ? for a stale issue absent from the complete cached listing; stale or
drifted links are highlighted, and +N summarizes additional links. Closed beads are
loaded but hidden by the visible -status:closed limit:<page_size> default; press f
to edit or clear that query. Section headings report matched and total counts while a
filter is active.
n collects a standalone task for the selected project. Title, filing reason, size, and
task type are required. The reason is one or two sentences, at most 2000 characters, and
is stored separately from the title and description. Ctrl+S creates the bead and Esc
cancels. See Creation Reason.
The pane supports the full bead workflow:
| Key | Action |
|---|---|
j / k |
Select the next / previous bead |
Enter |
Open the complete bead detail in the preview reader |
f |
Edit the bead filter query |
Ctrl+J / Ctrl+K |
Load more matching beads / unload one page (rewrites limit:) |
l / h |
Expand / collapse the selected epic |
s |
Cycle the selected bead's status using the type-aware sequence below |
z |
Snooze the selected task bead (or edit/cancel an existing snooze) |
e |
Edit the bead's valid fields |
N |
Append a note; @path attaches a snapshot and Ctrl+T cycles its audience |
n |
Create a task bead in the selected project (filing reason required) |
c |
Close with a required reason and optional note, or reopen a closed bead |
w |
Launch an epic or launchable task; phase work launches with its epic |
E |
Open a linked external issue |
y |
Copy the bead's @bead: reference |
% u |
Copy a linked issue reference (copy mode) |
b |
Enter issue-action prefix mode |
$ |
Arm the link rail, e.g. to follow the linked plan |
R |
Open the Refresh panel |
N opens a modal for appending a note and leaves earlier notes in place. Add @<path>
to capture a file snapshot when you save with Ctrl+S: Tab completes paths, pasting a
single existing file path inserts an attachment reference, and @@ inserts a literal
@. Ctrl+T cycles the attachment audience through automatic, private, and public.
Public asks you to confirm on save; policy can still reject the note and return you to
the editor. The bead detail lists each attachment's 🌐 or 🔒 chip from the stored
descriptor and leaves availability badges and thumbnails to the attachment viewer. See
From the Beads pane.
When a bead has several issue links, E, % u, and the b-mode v, e, s, and u
actions first open a selector. In b prefix mode, press v to view the cached body,
e to edit supported title/body/label fields, s to close or reopen after
confirmation, u to copy the provider URL, a to attach an existing numeric issue, or
c to create and attach a new issue. Attach and create operate on the bead itself and
therefore do not select an existing link. Availability follows the active VCS provider's
capabilities. The detail reader shows each link's relation, state, labels, assignees,
author, update/comment metadata, and cached body. The info line reports linked,
remote-only, stale, and drifted counts, plus provider unavailability and per-project
errors, so tracker health remains visible without a separate Bugs pane.
Closing offers done, canceled, and superseded resolutions. A bead with unfinished
descendants is rejected unless the close modal's force option is enabled with a
non-done resolution; the modal previews those descendants first. Closing or launching
a task with a pending TaskTriage request settles that gate so the notification does not
outlive the decision. Mutation work runs as tracked procs and refreshes the pane after
completion.
The default s status action is type-aware:
task: open → ready → in_progress → closed → open
claimed → ready
snoozed → ready
other beads: open → in_progress → closed → open
The s action changes persisted status only; moving a task from ready to
in_progress does not itself launch a worker. Use w, the matching TaskTriage
notification, or sase bead work <task-id> to launch the work. The editor shows only
fields valid for the selected bead type, while dependencies remain read-only in the
detail panel. s only cycles a task out of snoozed (back to ready, canceling the
snooze); entering snoozed needs z, since snoozing takes arguments (a wake time, and
optionally a +1 target and a reason) that a blind status cycle cannot supply.
Press z on a task bead to open the snooze picker: presets for 4 hours,
Tomorrow morning, 3 days, and 1 week, plus a custom duration field accepting the
same "<duration> [+<N>]" vocabulary as sase bead snooze (see
Snoozing a Task Bead) and an optional reason field.
Pressing z on an already-snoozed task opens the same modal in re-snooze mode, showing
the current wake time and offering a cancel-snooze choice. A snoozed row in the list
shows the snoozed status glyph and a dim relative wake time.
When that worker appears on the Agents tab, its bead badge points directly to the task, and its SASE CONTEXT / BEAD lane shows the task title and description, plus size when one is stored, without trying to resolve an epic plan.
Agent Pane¶
Artifacts → Agent is the durable historical catalog. It is intentionally different from the top-level Agents tab: the live tab is the operational view for running work, unread completions, and current clan/session panels, while this pane keeps queryable rows for prior runs, dismissed agents, sessions, clans, workflows, and workflow children. Rows are newest-first. The pane paints its newest 500 rows first, then extends to the complete catalog and builds the full query index off-thread.
The left side defaults to Session grouping; o / O cycle forward or backward
through Session, State, and Project. The shared fold keys (h, l, H,
L) collapse or expand group banners. Selecting a row updates the right-hand detail
with its durable agent: reference, identity, lifecycle and timing, model/provider,
session and retry lineage, provenance, a bounded prompt preview, chat path, and hosted
agent page when those files resolve.
The collapsed relation rail combines catalog lineage—session, clan, parent, and retry
chain—with typed artifact links such as cites, read, implements, and their inverse
labels. Expand it with ., then use the printed <, >, or ~ mode and row hint to
navigate. linked:true, relation:<slug>, and artifact:<canonical-ref> expose the
same graph as query facets.
| Key | Action |
|---|---|
j / k |
Select the next / previous catalog row |
/ |
Edit the Boolean Agent query |
o / O |
Cycle grouping forward / backward |
w |
Revive the selected dismissed row, or the marked revivable rows |
m / u |
Mark / unmark the selected row · clear this pane's marks |
Ctrl+J / Ctrl+K |
Load more matching agents / unload one page (rewrites limit:) |
p |
Change the shared Artifacts project scope |
% |
Open the Agent Copy as… palette |
R |
Refresh the catalog (through the Refresh panel) |
w skips marked rows that are not revivable. On an aggregate session or clan with one
revivable member it revives that member directly; with several, it narrows the pane to
the matching state:dismissed AND revivable:true session or clan query so the choice is
explicit. Use sase agent search for the same catalog
dialect outside sase's TUI.
Document Provider Panes¶
Artifacts includes one document pane per configured artifact-reference provider. The
fixed tabs are Agent, Stitch, Patch, Bead, and File; provider-backed document tabs such
as Plan and Research appear between Bead and File when an enabled project configures the
matching sidecar ref: policy. Persisted selections use stable ids such as ref:plan
and ref:research, so a missing provider falls back to Stitch instead of crashing
startup. Each pane renders an icon before its label — the five fixed marks (⬡ Agent,
◉ Stitch, ⎇ Patch, ◈ Bead, ▤ File) are built in, and a provider pane's mark
comes from its sidecar ref.icon. The active tab's icon takes that pane's accent color;
inactive icons render dim. A missing ref.icon, one that fails validation, or one wider
than two terminal cells uses the generic ◆ provider icon instead.
Plans is the built-in provider-backed document pane for the plans sidecar. It keeps the
existing plan actions: A and X approve or reject pending proposals. A plan's owning
bead (and a bead's linked plan) is an artifact link, so follow it from the
link rail with $. Plans groups its list by Kind,
Status, or Project (o / O). Other document providers reuse the same list,
filter, detail, preview, copy, and refresh behavior from their declared properties and
detail fields, and group only when they declare ref.grouping (or ref.pane.group_by);
see Grouping declarations.
Commit Detail and Linked Plans¶
Press Enter on a Stitches entry to open its full message and syntax-highlighted diff.
The modal uses j / k for line scrolling, Ctrl+D / Ctrl+U for half pages, g /
G for the ends, y to copy the full SHA, and Esc or q to close. When the current
result contains multiple commits, Ctrl+N / Ctrl+P move through them with wraparound.
The modal's header block carries a compact commit-time chip, right-aligned on the line
that also shows the diff path — an absolute stamp plus a relative age, such as
Today 07:05:54 · 2h ago. Today and yesterday show seconds; older commits show HH:MM
after their day label. Times are the commit's author time in the configured local
timezone. The chip is free on the Artifacts timeline, where the commit time is already
loaded; on the Agents tab, a commit whose stored metadata records no time has it looked
up from the VCS alongside the diff, so the chip appears once that load lands. Commits
made by SASE persist their author time in commit_result.json / commit_results.json,
so later views need no lookup at all. A commit whose time cannot be resolved simply
shows no chip.
Press p in the commit modal to load the last structured SASE_PLAN footer tag and
render its referenced local UTF-8 file as Markdown; press p again to return to the
cached diff. This is a local-only lookup: an absolute path is expanded and checked
directly, while a relative path is checked first in the commit repository, then in each
known project workspace and its plans store. sase's TUI does not clone or materialize a
missing store. A missing tag, invalid reference, unavailable path, non-file path, or
unreadable file produces a specific toast and leaves the commit visible. Moving to
another commit always returns the modal to commit mode.
Preview Reader¶
Press Enter on a Beads entry, provider document, or Files row to open its full
contents in the preview reader. Prompt-normal-mode K opens the same reader for a
previewable macro, skill, or file. When sase's TUI knows a canonical artifact reference,
the title shows that logical reference beside the resolved local path.
The reader opens at its normal size when the content fits and grows toward a
near-full-screen maximum (a thin backdrop margin remains) for long or wide content.
Width follows the 95th-percentile line width, so one very long line does not force full
width. Switching views with R or p only ever grows the panel; resizing the terminal
recomputes it.
| Key | Action |
|---|---|
j / k |
Scroll down / up one line |
Ctrl+D / Ctrl+U |
Scroll down / up half a page |
g / G |
Jump to the top / bottom |
y |
Copy the complete preview contents |
Y |
Copy the local source path, when available |
% |
Open the active Artifacts sub-tab's Copy as… palette |
R |
Toggle Markdown previews between rendered and source views |
p |
Toggle the full macro properties view |
/ |
Open source search (smartcase substring matching) |
n / N |
Jump to the next / previous match with wraparound |
o |
Open the source path in $EDITOR (falling back to nvim) |
Z |
Hand the source path to the rich terminal artifact viewer |
Esc |
Cancel input, clear active search, then close the reader |
q |
Close the reader |
Path-only actions are omitted from the footer when the preview has no local source path; invoking one still produces a specific warning instead of failing. Clipboard operations run in the background and report when no clipboard tool is available. Plans open in rendered Markdown by default when they fit the reader's bounded render budget; macros, skills, files, and oversized documents open as source.
For a macro or skill preview with declared properties (inputs, tags, skill/snippet/
memory flags, local macros, or steps), a compact band appears above the source pane
showing its description, an inputs table, and a dim chips summary of everything else it
declares. p opens a full, scrollable properties view — the same projection
sase macro show renders — with the band hidden while that view is active; p again
restores whichever mode was showing before. A preview with no declared properties (a
bare-body macro, a plain file, a bead, and so on) shows no band, no p row in the
footer, and p emits a warning toast instead of switching views.
Search is commit-on-enter: / opens a one-line input prefilled with the last committed
query, and Enter highlights every matching source line before jumping to the first
match at or below the current viewport. Queries containing an uppercase character are
case-sensitive; other queries are case-insensitive. Starting search from rendered
Markdown switches to source view. Esc in the input cancels the edit, while Esc after
a committed search first clears the matches and only closes the reader on the next
press.
Prompt K on a supported image path (.png, .jpg, .jpeg, .webp, .gif) opens
the reader with an inline cell preview sized to the near-full-screen panel. It
re-renders on terminal resize. The footer shows
Y path | % copy | Z viewer | esc close, and Z opens the full-fidelity artifact
viewer. y, /, and o warn instead of acting on image previews.
File Pane¶
Files browses the artifact-file index that backs sase artifact list. The
pane is one of the five fixed Artifacts views: Agent, Stitch, Patch, Bead, and File.
Configured document-provider panes appear between Bead and File in the runtime tab
strip. Rows are grouped by logical file identity, so repeated captures of
@file:~/bob/gtd.md or repeated sase artifact create rows for the same logical
artifact appear as one selectable row with versions.
Each row shows a view-mode glyph, the project, the producing agents, an origin badge, the logical label, the latest selected version's timestamp, and the indexed size.
| Glyph | View mode | Opens with |
|---|---|---|
▨ |
image |
The rich terminal viewer (kitten icat) |
▶ |
video |
The rich terminal viewer (mpv) |
▤ |
pdf |
The rich terminal viewer (PDF pages rendered to PNG) |
▤ |
markdown |
The preview reader, rendered by default |
• |
text |
The preview reader, as source |
The glyph comes from the same classifier that chooses the viewer, so it can never
disagree with what Enter actually opens. Origin badges distinguish files cited in a
prompt (ref), files registered explicitly with sase artifact create (created), and
automatic captures (capture).
The info line above the list summarizes the loaded snapshot as kind chips and origin
chips, where documents totals PDFs and Markdown. The index loads off the message pump
in two pages: a first bounded page paints immediately, then the full index replaces it,
and the status line reports both the loaded row count and any in-flight extension.
Selecting a row loads its detail panel off-thread. The detail header shows
version i/n, the durable file:<id> or logical @file reference, digest, capture
time, project, agent, origin, MIME type, and size. Markdown and text rows include a
bounded preview. (/) cycle the selected row's versions without moving to another
logical file; repeated captures with the same SHA-256 share one version and accumulate
provenance.
| Key | Action |
|---|---|
j / k |
Select the next / previous file, skipping day headings |
Enter |
View the file: preview reader for Markdown and text, rich viewer for media |
Z |
Hand the marked visible rows — or the selection — to the rich terminal viewer |
E |
Open text in $EDITOR (falling back to nvim); open media with xdg-open |
a |
Jump to the producing agent on the Agents tab, reviving it first when dismissed |
f |
Edit the pane's filter query |
Ctrl+J / Ctrl+K |
Load more matching files / unload one page (rewrites limit:) |
z |
Cycle the kind filter through All and the stored kinds present in the snapshot |
o / O |
Cycle grouping (Source, Kind, Project) forward / backward |
( / ) |
Select previous / next version for the current logical file |
y |
Copy the row's @file:<id> reference |
Y |
Copy the row's anchored stored path |
m / u |
Mark / unmark the selected file · clear this pane's marks |
% |
Open the Files Copy as… palette |
R |
Refresh the index (through the Refresh panel) |
p |
Change the shared Artifacts project scope (first-open seeds current project) |
These are the default keymap values; the Files-pane actions retain their files_*
configuration names and are remappable under
ace.keymaps.app as files_next, files_prev,
files_view_selected, files_open_viewer, files_open_external, files_open_agent,
files_filters, files_cycle_kind, and files_copy_path. y/R are the shared
artifacts_copy_reference/refresh actions every Artifacts pane binds. Ctrl+J /
Ctrl+K are the shared artifacts_load_more/artifacts_unload actions. Version
cycling uses the old nested-files sub-tab keys. The pane also shares the navigation and
jump keys described in
Navigation in Agent, Stitches, Beads, Provider Documents, and Files.
Y copies the anchored stored path, except that PDF rows deliberately yield the live
Markdown source they were rendered from when the index recorded one. Relative index
paths are anchored to the producing workspace, so a copied path is always usable outside
the workspace that created it, and the completion toast says when the copied path no
longer exists. The palette previews that same preferred path, so what a PDF row shows is
what %p copies. % adds the rest of the Files-pane copy targets: contents, Markdown
link, source path, label, and metadata JSON, each of which also operates on the marked
set.
a resolves the producing agent from already-loaded live and dismissed agents by
artifact directory, then by raw name suffix, then by recorded agent name, always within
the row's own project. A file whose source workspace was recycled still opens and still
copies — only its Source path reports missing.
Filtering Files¶
Files keeps its committed query visible in a persistent filter row, the same idle chrome
Agent, Patches, Stitches, Beads, and Plans use. Press f or / to edit it. Filtering
is purely in-memory over the loaded snapshot, so a query narrows thousands of rows
without a re-query. Files accepts id:, kind:, project:, agent:, workflow:,
origin:, since:, until:, and host-owned limit:, plus free text matched against
the label, logical path, stored path, source path, digest, and artifact id. id: is the
pane's identity field: it matches a logical file id exactly and stays free-text
searchable too. Tokens from different facets combine with AND semantics, while
comma-separated or repeated values within id:, kind:, project:, agent:,
workflow:, and origin: combine with OR semantics. kind: accepts the stored kinds
chat, plan, image, markdown, pdf, and file; origin: accepts ref,
created, and capture; since: and until: accept YYYY-MM-DD, YYYY-MM,
YYYYMM, or a relative Nd / Nw / Nm offset and may each appear once.
z and the kind: token drive the same filter state: cycling with z closes an open
edit session and sets kind: to the next stored kind actually present in the snapshot,
wrapping back to All. A query listing several kinds is treated like All, so the next
press selects the first present kind. When a filter hides every row, the pane says so
and names the key that focuses the query row.
Epic phase sizes across plan surfaces¶
sase's TUI uses the literal scope labels xsmall, small, medium, large, and
xlarge, with mint, sky, gold, rose, and violet chips whose text remains the primary
signal. Valid older plans and phase beads with an omitted size use the stable small
fallback, while an invalid authored value never produces a confident chip or count. The
Plans pane also uses that shared display fallback for a legacy standalone task with no
stored size; launch routing uses the same @small fallback.
| Surface | Size contract |
|---|---|
| Agents author and lander | Shows every normalized authored size in roadmap order. |
| Agents phase worker | Shows only that worker's normalized authored size, preserving phase isolation. |
| Artifacts / Beads epic, phase, and task beads | Shows current persisted bead sizes in rows and details; the epic detail summarizes direct children in xsmall, small, medium, large, xlarge order. Standalone tasks appear in their own section. A legacy task with no stored size displays and launches through the shared small fallback. |
| Artifacts proposals, linked plans, and archives | Retains the authored phases property exactly once instead of adding a competing roadmap. |
| Telegram epic review | Adds a validated textual size breakdown while retaining the detailed Properties card and source/PDF attachment. |
Epic clan summary, sase bead show, and epic work preview |
Continues using persisted bead sizes, which these execution surfaces already exposed. |
| Raw approval, validation/schema, source/PDF, and mobile attachment paths | Remains a lossless generic/source view; authored phase metadata is preserved without a second summary. |
Keybindings: Artifacts / Patches¶
Navigation¶
| Key | Action |
|---|---|
j / k |
Move to next / previous visible row (banner at fold < L2, PR at the leaf level) |
< / > / ~ |
Navigate to ancestor / child / sibling PR |
' |
Jump by adaptive hint (current tab); hints land on collapsed banners too |
Ctrl+O / Ctrl+Shift+O |
Walk the link trail first, then the current-tab jump stack; back falls through to first hint |
$$ / $1-$9 / $0 |
Follow the first / numbered artifact link, or open the complete links panel |
` |
Jump to entry across all tabs (see Jump All Modal) |
o / O |
Cycle PR grouping mode forward / reverse (BY_PROJECT ↔ BY_DATE ↔ BY_STATUS) |
g / G |
Scroll detail panel to top / bottom |
Ctrl+D / Ctrl+U |
Scroll detail panel down / up (half page) |
{ / } |
Narrow / widen the shared Artifacts list panel (with wraparound) |
Ctrl+J / Ctrl+K |
Load ace.page_size more rows / unload that page (rewrites the host limit: cap) |
Note:
oopens a direct grouping picker on the Agents tab.o/Ocycle the L0 grouping bucket forward / reverse on Artifacts panes that have a grouping mode (each surface keeps its own in-session mode). Beads has no grouping modes, and a provider document pane has them only when it declaresref.grouping, so the keys are a silent no-op on panes without modes; the same is true on the Services tab. The Artifacts open-externally verb moved toE; bang-mode!ostill marks PR origin. See PR Grouping and Folding and the Agents-tab Grouping Modes below.
PR Actions¶
| Key | Action |
|---|---|
A |
Accept proposal (! = spec only, @ = mark ready to mail) |
b |
Rebase PR onto parent |
C / c1-c9 |
Checkout PR (primary / workspace 1-9) |
d |
Show diff (Patches sub-tab only; d toggles sidecars on Stitches and the Services description) |
e |
Edit spec file |
f |
Edit the Patches filter query |
F |
Edit hooks (re-run / delete via hint input; type . to pick from hook history) |
M |
Mail PR |
m |
Mark / unmark current PR (auto-advances to next) |
n |
Rename PR (non-Sub/Rev PRs only) |
!o |
Mark PR origin (sase/external/unknown) |
!R |
Rewind to previous commit (! suffix skips VCS operations) |
R |
Open the Refresh panel (refreshes immediately when it is disabled) |
y |
Copy the PR's @patch: reference |
s |
Change status (opens status modal) |
S |
Bulk status change for all marked PRs |
T / t |
Checkout + tmux (primary workspace / prompt for a workspace number) |
u |
Clear all marks |
v |
View files (hint mode) |
V |
Open the Agent Run Log modal for the current PR |
w |
Reword PR description |
W |
Add tag to PR description |
x |
Show/hide submitted PRs |
X |
Show/hide reverted PRs |
Y |
Sync workspace |
PR Grouping and Folding¶
The Patches sub-tab is always grouped — the renderer walks one of BY_PROJECT,
BY_DATE, or BY_STATUS and emits a banner row above each bucket. BY_PROJECT is the
startup default; o cycles BY_PROJECT → BY_DATE → BY_STATUS for the current session.
| Mode | L0 buckets | Notes |
|---|---|---|
BY_PROJECT |
Project name | Adds an L1 sibling-root sub-banner shared by foobar_1 / foobar_2 style suffixed siblings. Singletons suppress their L1 banner. |
BY_DATE |
Today / Yesterday / This Week / Earlier |
Bucket from the latest TIMESTAMPS entry. Today/Yesterday add 4-hour L1 windows; hourly L2 headings appear only inside 4-hour windows with 2+ PRs. This Week adds day headings; Earlier adds week headings plus (no timestamp). |
BY_STATUS |
Mailed / Ready / WIP / Draft / Submitted / Reverted / Archived |
Bucket from the literal status field; actionable buckets first (Mailed = awaiting response, Ready = next to mail), terminal states last. Adds an L1 sibling-root sub-banner shared by foobar_1 / foobar_2 style suffixed siblings inside each status bucket. Singletons suppress their L1 banner. |
In BY_DATE mode, PRs sort newest-first within each date bucket. Today and
Yesterday are grouped first by compact 4-hour windows (8AM-12PM); one-hour headings
(09:00) appear only when that 4-hour window contains at least two PRs. This Week
uses calendar-day subgroups; Earlier uses Monday-start week ranges. PRs without a
parseable TIMESTAMPS entry fall into (no timestamp) under Earlier.
The active grouping mode is shown in the Patches sub-tab's info-panel header as a
[group: <label>] badge.
| Key | Action |
|---|---|
l |
Expand the focused banner one level (or peel one layer of the visible tree) |
h |
Collapse the focused banner; on a collapsed L1 banner, escalate to its parent. With agent focus, collapse the deepest enclosing group. |
zL |
Snap to fully expanded — all banners and Patch rows visible (z fold-mode prefix; bare L does nothing on Patches) |
H |
Snap to fully collapsed — collapse every visible banner |
Collapsed banner rows are first-class navigation stops: j/k step through them just
like Patch rows, and ' jump-hints land on them too. After a fold change that hides the
focused PR, focus snaps to the deepest collapsed ancestor banner so the cursor always
sits on a row the user can see.
Fold Mode (z prefix)¶
| Key | Action |
|---|---|
z c |
Cycle stitches section (expand → collapse) |
z d |
Cycle deltas section (summary → files → line counts) |
z h |
Cycle hooks section (expand → collapse) |
z m |
Cycle mentors section (expand → collapse) |
z t |
Cycle timestamps section (expand → collapse) |
z C |
Toggle stitches section (collapsed ↔ fully expanded) |
z D |
Toggle deltas section (summary ↔ line counts) |
z H |
Toggle hooks section (collapsed ↔ fully expanded) |
z M |
Toggle mentors section (collapsed ↔ fully expanded) |
z T |
Toggle timestamps section (collapsed ↔ fully expanded) |
z z |
Cycle all sections |
z Z |
Toggle all sections (expand ↔ collapse) |
z 1 |
Set every section to collapsed (level 1) |
z 2 |
Set every section to expanded (level 2) |
z 3 |
Set every section to fully expanded (level 3) |
z L |
Expand every grouping banner |
STITCHES, HOOKS, MENTORS, and TIMESTAMPS sections each cycle through three fold levels:
| Level | Behavior |
|---|---|
| Collapsed | Notes truncated to fit; multi-line body shown as [+N lines]; only latest drawers |
| Expanded | Full notes; body shown in dimmed text; all CHAT/DIFF/PLAN drawers visible |
| Fully Expanded | Everything visible including rejected proposals |
The lowercase cycle keys (z c, z h, z m, z t) step through all three
levels in order. The uppercase toggle keys (z C, z H, z M, z T) skip the
intermediate Expanded state, jumping directly between Collapsed and Fully
Expanded.
When collapsed, a [folded: CHAT + DIFF + PLAN + N proposals] indicator appears on
STITCHES entries with hidden content. The indicator width is pre-calculated so that note
truncation accounts for it. TIMESTAMPS shows a [folded: N] indicator inline with the
header and displays the most recent timestamp entry when collapsed, giving a quick view
of the last lifecycle event.
The DELTAS section uses the same three levels with its own meaning. When collapsed,
the section renders a one-line file and line-count summary such as
DELTAS: +3 (+428) ~6 (+91 ~37 -14) -1 (-22) (10 files). When expanded, the
alphabetical entry list is shown with colored glyphs (green +, gold ~, red -);
fully expanded adds inline line-count tokens to each entry. Binary files display
binary; zero-count entries display 0 lines. z D jumps between the summary and
the full list. The section is omitted entirely when the Patch has no deltas.
Workflows and Agents¶
| Key | Action |
|---|---|
r |
Run workflow on current PR |
+ |
Run a custom agent (opens project/Patch selection) |
Space |
Prefill the prompt with the most recently launched VCS macro (blank home prompt if none; Space then Ctrl+U for a blank prompt) |
,<space> |
Run an agent from the current PR (skips selection) |
Ctrl+G |
Open that most recent VCS macro in $EDITOR first |
@ |
Restore a stashed prompt |
If sase's TUI cannot detect a workspace provider for the selected Patch or agent, the quick-launch actions show an error toast instead of opening a prompt with a broken VCS prefix.
Bang Mode (! prefix)¶
| Key | Action |
|---|---|
!! |
Run a background command (choose a project, a workspace, then a command) |
!x |
Start / stop service host (or select process) |
!o |
Mark PR origin (sase/external/unknown) |
!R |
Rewind to previous commit (! suffix skips VCS operations) |
!! ends in a Run Command picker over previously run background commands: type to
filter the history or enter a new command, move with Ctrl+N / Ctrl+P or the arrow
keys, press Enter to run it, and Esc to cancel. ,! skips the project and uses the
current PR's project.
A background command is a oneshot service proc: a durable proc-store row with a
recorded exit code, a store-owned log, and a stable #1–#9 index, shown in the
Services tab's ── oneshots ── section. It runs outside the TUI and the service host,
so quitting or restarting either does not kill it, and a rerun (r) works after a TUI
restart. At most nine oneshots can be running at once; finished commands never block a
new one (the oldest finished index is reused once all nine are taken).
sase service proc run submits through the same path.
Hook History Modal¶
Type . into the F edit-hooks input to open the hook history modal, which lists
previously added hook commands:
| Key | Action |
|---|---|
j / k |
Navigate through hook history |
Enter |
Add the highlighted hook to the current Patch |
Ctrl+D |
Delete highlighted hook from history |
Ctrl+G |
Edit first — select hook and open in input |
Esc / q |
Cancel and close modal |
The modal supports live filtering as you type in the search box and displays last-used timestamps for each hook.
Leader Mode (, prefix)¶
Help is not a leader command: press the app-level ? on any tab to open the Help modal.
| Key | Action |
|---|---|
,, |
Repeat the last leader command |
,! |
Run command using current PR context |
,A |
Open the Agent Run Log modal for the current PR |
,c |
Clear COMMENTS field (kills CRS agents, deletes CRS proposals) |
,C |
Review mentors (opens Mentor Review modal) |
,h |
Run agent from home prompt context; bare prompts default to #git:home |
,m |
Open Launch Control (aliases, providers, tmux Agent; see Launch Control) |
,U |
Open Update panel (SASE, providers) |
,E |
Plan Everything from cached update snapshots and skip confirmation if runnable |
,L |
Jump to the log entry for the most recent error toast |
,M |
Kill running mentors |
,R |
Show runners info |
,<space> |
Run agent from current PR (skips project selection) |
,. |
Open the Prompts overlay on History |
,Ctrl+G |
Edit the newest prompt-history entry in $EDITOR (no overlay) |
,> |
Open the Prompts overlay on History with cancelled prompts visible |
,@ |
Open the Prompts overlay on Stash without auto-restoring a lone entry |
The ,h shortcut opens a home-context prompt directly. Project and PR launch pickers
use lifecycle-aware discovery: project entries, including home when it appears in
picker lists, must have enabled and launchable ProjectSpecs; PR choices come from
enabled ProjectSpecs. Disabled projects do not appear in normal launch pickers until
they are enabled with sase project enable <project>. You can also type a known-project
VCS ref explicitly; launch preparation treats that as intent to resume work and
re-enables the project before claiming a workspace. When the current project appears in
the list, the + picker highlights that row on mount; typing in the filter box still
resets the highlight to the first match.
Project launch pickers also support Ctrl+D for cleanup of empty project entries. This
deletes only the highlighted project's active/archive ProjectSpec files, refuses entries
whose ProjectSpec files still contain Patches, and does not delete workspace checkouts
or other SASE state. For lifecycle changes, bulk operations, ProjectSpec editing, or
deleting the whole SASE project directory, use the Projects tab of the SASE Admin
Center (press #).
The repeat binding is the leader prefix followed by the configured repeat_last key.
With the defaults both are comma, so the sequence is ,,; if the leader prefix is
changed but repeat_last is not, the second key remains comma. Repeat re-dispatches the
last recognized leader subkey against the current tab and selection. If no leader
command has been run yet, sase's TUI shows a toast and does nothing.
Note:
,x(kill & edit) and,X(kill & edit last launched agent) are only available on the Agents tab — see Agents Tab Leader Mode.
Mentor Review Modal¶
Press ,C to open the Mentor Review modal, which lets you navigate mentor comments,
accept or reject suggestions, and apply accepted changes. See
docs/mentors.md for the full mentor system reference.
| Key | Action |
|---|---|
j / k |
Navigate between mentors |
n / p |
Navigate between comments within a mentor |
N / P |
Navigate between accepted comments only |
Ctrl+D / Ctrl+U |
Scroll comment details down / up |
Space |
Toggle acceptance of the current comment |
a |
Apply accepted comments and propose (amend with propose) |
A |
Apply accepted comments and commit |
r |
Run a mentor profile (opens profile picker) |
y |
Copy the current comment to the clipboard |
Shift+K |
Kill a running mentor |
Esc / q |
Close modal |
Copy Mode (% prefix)¶
Press % to open the Copy as… palette for the selected Patch. Select a row with the
mouse, arrows or j/k and Enter, or complete any configured two-key accelerator
directly. q/Esc cancels; configured target keys take precedence if rebound to j,
k, or q.
| Key | Action |
|---|---|
%% |
Copy Patch |
%! |
Copy Patch + snapshot |
%b |
Copy bug number |
%c |
Copy PR number |
%n |
Copy PR name |
%@ |
Copy @patch: reference |
%l |
Copy Markdown link |
%p |
Copy project spec file |
%s |
Copy sase tui snapshot |
Keybindings: Agents Tab¶
Machines¶
When at least one remote machine is enrolled, the Agents tab shows local and remote rows
in one list. Remote agent, session, and clan nodes carry a short host-alias chip such as
apollo or mac; local rows never carry a here chip, including under a by-machine
group header. A fleet header row appears above the list only when there is something to
act on. A fleet configuration or follow-store error is shown on its own; otherwise the
row joins machine diagnostics and machine feed errors with ·. Diagnostics naming one
or two machines read apollo unknown (or apollo, mac unknown); anything else reads as
a diagnostic count such as 3 machine issues. One machine feed error reads
<alias>: feed <status>: <detail>, with (cached 5m ago) when a cache backs it; two or
more read 2 machines with feed errors. Otherwise the row stays hidden. An entirely
local setup keeps the compact local view and exposes the machine connection route.
Remote rows use the same visible operations where the owner advertises support: machine
status, retry, bounded remote content, pending question/gate handling, stop, and fork.
The normal x stop and F fork actions work on capable remote rows; actions that
require local files, tmux, or local Patch state stay unavailable. Remote mutations are
durable requests, optimistically annotate the row while in flight, and refresh the
Agents list after they settle. Setup and recovery commands are covered by the
Remote Dispatch Runbook.
Remote agents are grouped into the same session and clan nodes as local ones, with the
same queue badges (cN, wN) and the same session and clan status rules. A remote row
stays free of connection chrome while its machine's feed is online and fresh; anything
else is shown on the row. A stale feed reads like stale · cached 5h ago, and an
invalid feed reads feed invalid, is named in the Agents header, and adds a Feed
error line to the detail panel, so cached data never looks silently healthy. A remote
row the owner reports as WAS RUNNING shows how long ago it was last seen.
Agent Tabs¶
The Agents tab shows one agent tab at a time instead of the whole roster. A tab is a
presentation-only placement: it never changes where an agent runs, its identity, clan,
session, or tribe. Tabs come from each root's recorded tab (see
Tab Directive): %tab:<name> authors a named tab, and roots
without one land on the default tab. The default tab is labeled main, or
⌂ <machine name> in machine mode (ace.agent_tabs.machine_tabs: on, or auto when
dispatch machines are configured), where <machine name> is the viewer's
id.machine_name (⌂ local when unset). In machine mode each remote machine gets its
own ⌨ <alias> tab; %tab:apollo is still an ordinary named tab, distinct from
⌨ apollo. A remote alias literally named local, or equal to the local machine name
(case-insensitive), renders as ⌨ <alias>·remote.
A tab exists while at least one visible root resolves to it, and the strip beside the
fleet status appears once two or more tabs exist (with a single tab the Agents tab looks
exactly as before). The active tab shows as a pill; inactive tabs show their label,
count, and only the non-zero attention tokens (S/F/U). Machine tabs carry a ⌨
glyph whose label turns amber when its host is stale and red when it is invalid or
offline. The local machine tab carries a teal ⌂ home glyph instead. An arrival dot
(•) marks an inactive tab that gained a new root since you last visited it. At narrow
widths the strip compacts and then windows around the active tab with ‹N / N›
overflow chips that open the tab picker.
] / [ cycle tabs (wrapping, including overflow tabs). The palette entry
Agents: go to tab…, the ' jump hints on strip chips, and clicking a chip or overflow
chip all switch tabs too. Every cross-tab jump switches to the target's tab first and
then reveals it: Node Finder searches all tabs and chips off-tab rows, ,j / ,J
consider every tab, and notification, link, monitor, run-log, and Files jumps all land
cross-tab. Choosing a tab while the All-tabs layout level is active drills into that
tab. Selection, folds, and sticky panels are remembered per tab, and the active tab
persists across restarts. If the active tab's last agent leaves, the tab stays selected
with an empty state until you navigate away; other empty states name a hiding query
(with a clear-filter hint) or an unavailable feed.
Bulk confirmations name their scope while the strip is visible: panel-wide actions and
the custom selector read on <tab> (for example on sase), across all tabs only at
the all-tabs scope, and a marked cleanup that spans tabs adds an
N of M marked agents are on other tabs line. Marks themselves stay global. Tab
styling, ordering, and machine-mode behavior live under ace.agent_tabs (see
docs/configuration.md).
Launching from a named tab inherits it: the prompt bar shows a tab: <name> chip and
the submitted prompt gains %tab:<name> (opt out with %tab:main or
ace.agent_tabs.launch_from_view: false). gb opens the Launch Tab picker to insert or
replace %tab. A launch that lands on another tab toasts its destination
(blog-fix → blog) and lights that tab's arrival dot. Move agents between tabs with
sase agent tab list|set|unset (see
Moving agents between tabs) or the N
Tribe & Tab modal, which completes known tabs and applies optimistically with rollback;
rows owned by a remote machine cannot be moved. Filter the roster with the tab: query
field (exact match, or main for the default tab).
Navigation¶
| Key | Action |
|---|---|
j / k |
Move to the next / previous visible row; while a whole panel is selected, or when the focused panel has no other selectable row, cycle whole panels instead |
J / K |
Cycle focus across expanded tribe side panels (forward / reverse) |
' |
Jump to a row, collapsed grouping banner, or split-panel title by adaptive hint |
Ctrl+O / Ctrl+Shift+O |
Walk the link trail first, then the current-tab jump stack; back falls through to first hint |
$$ / $1-$9 / $0 |
Follow the first / numbered artifact link, or open the complete links panel |
Ctrl+J / Ctrl+K |
Next / previous card in the focused deck panel (wraps; sets the panel's preferred card) |
( / ) |
Older / newer card block in the focused deck panel (wraps; only when the shown card has 2+ blocks) |
] / [ |
Next / previous agent tab (wraps; only while the strip shows two or more tabs; see Agent Tabs) |
| (palette) | Pick an agent tab from the command palette (Agents: go to tab…; same strip gating as ]/[) |
P |
Cycle the focused panel's deck view wider, pinning it fixed (wraps; Main and Files only, only when there is more than one distinct layout) |
` |
Jump to entry across all tabs (see Jump All Modal) |
" |
Find and jump to any node, including hidden ones (see Node Finder) |
0–9 |
Jump from a selected clan, agent node, session member, or whole-panel roster to its numbered member or neighbor (live in the jump panel below the detail panels) |
o, then p/d/s/m |
Choose grouping mode: Project, Date, Status, or Machine |
oo |
Step the Agents panel layout ladder forward, then close the grouping picker (see Grouping Modes) |
g |
Scroll to top (focused deck panel) |
G |
Scroll to bottom (focused deck panel) |
Ctrl+D / Ctrl+U |
Scroll the expanded jump panel when its content overflows, else the expanded header panel when its content overflows, else the focused deck panel (half page) |
Ctrl+N / Ctrl+P |
Focused panel to the next / previous deck (Main → Files → Tools → FINAL, wraps) |
p |
Pick the focused panel's deck: m Main, f Files, t Tools, n FINAL; M/F/T/N show it in the other panel (opening one below if needed); pp/Esc close |
\ / \| |
Split deck panels stacked / side by side; the same key erases its divider, the other key nests a third panel or turns three panels |
} / { |
Grow / shrink the focused deck panel (split layouts only) |
Ctrl+F / Ctrl+B |
Focus the next / previous deck panel in reading order, wrapping (split layouts only) |
Ctrl+Shift+F / > |
Swap the focused panel's session with the next panel in reading order, wrapping; geometry and ratios stay with the slots and focus follows the content (split only) |
Ctrl+Shift+B / < |
Swap the focused panel's session with the previous panel in reading order, wrapping (split layouts only) |
Ctrl+Shift+D / Ctrl+X |
Close the focused deck panel; focus goes to the most recently focused survivor (split layouts only) |
Ctrl+T |
Turn the deck layout: transpose a two-pane split, or a three-pane T layout (split layouts only) |
Ctrl+S |
Toggle the node rail (persisted preference); while zoomed, restore the snapshot like Z |
Note:
oopens a direct grouping picker on the Agents tab.o/Ostill cycle the L0 grouping bucket forward / reverse on Artifacts panes that have a grouping mode (each surface keeps its own in-session mode). Beads has no grouping modes, and a provider document pane has them only when it declaresref.grouping, so the keys are a silent no-op on panes without modes; the same is true on the Services tab. The Artifacts open-externally verb moved toE; bang-mode!ostill marks PR origin.g/Gkeep their conventional vim-style scroll-to-top/bottom meaning on every tab. See Grouping Modes below.
The numbered NEIGHBORS rows use dotted agent-name relationships rather than Patch
sibling families. Relations are keyed on the name a row presents as its sase agent
name, so a session participates under its bare session name rather than its root
member's -- name. The section shows visible ancestors and descendants plus neighbors
from every dotted hood that contains the selected sase-agent name — including the hood
that matches that name exactly. For example, foo.bar.worker lists peers under
foo.bar and cousins elsewhere under foo, grouped deepest hood first, and a session
fam lists fam.helper as a descendant while fam.helper lists fam back as its
ancestor. Dotless names can still have descendants such as foo.child. A digit jump
target is resolved by stable identity and revealed through any clan, session, workflow,
or grouping folds before focus moves.
When a clan or a sase agent is selected, its jump panel assigns a fixed number to each
numbered row, up to 100 targets. A sase agent is a multi-member session container or a
single agent; sase-agent panels number their SESSION TURNS roster (when present) and
then their NEIGHBORS section from one continuous ladder. Clan panels number their
CLAN MEMBERS roster and then their CLAN NEIGHBORS section from one continuous
ladder. A selected session turn row numbers its enclosing session's SESSION TURNS
roster the same way, listing every sibling except itself from the same ladder; a turn
row owns no sase agent, so it has no NEIGHBORS rows to follow the roster. Documents
with at most ten numbered rows use 0–9; larger documents number the first 100 rows
with two-key values 00–99 and show any remaining entries as an unnumbered count.
Every live numbered target lives in the sticky jump panel below the deck panels, so the
digit answers stay on screen while the deck body scrolls. The deck body no longer
contains these roster sections. After the first digit of a two-key jump, the panel
narrows to the matching candidates; press Esc to cancel or any non-digit key to cancel
and continue with that key's normal action. A successful jump expands only the target's
ancestor chain, switches tribe panels when needed, and participates in the normal
Ctrl+O jump-back history. A digit on a dismissed neighbor revives that agent instead
of jumping. If the roster or the neighbor relationship changed since the panel was
drawn, the jump is cancelled with a warning rather than landing somewhere stale.
In-flight targets, whose glyph is ▶ (running) or ◐ (starting), are lit: their number
chip runs into a softly tinted pill holding the bold name and glyph. This appears in the
collapsed legend, the narrowed candidates, and the expanded roster alike. When the
collapsed legend cannot show every target, it fills its slots with in-flight targets
first (still in number order), then the lowest remaining numbers. The +N overflow
count gains a lit ▶M when M in-flight targets remain hidden behind ..
Agent Actions¶
| Key | Action |
|---|---|
!R |
Revive a previously dismissed agent |
a |
Open completion artifacts for the focused agent; in tmux, press again to close the viewer pane |
+ |
Run custom agent |
A |
Toggle bare %auto plan auto-approval / auto-answer questions |
F |
Prepare a fork of the selected agent/session, named proc, monitor, clan container, or focused named tribe panel |
n |
Name agent |
r |
Refresh the Agents tab, or open the Refresh panel when that panel is enabled |
R |
Edit prompt and relaunch the selected local agent, or retry a remote row on its owner |
v |
View files (hint mode; annotates clan/session containers in place; adds ⚒ run log targets on the Runs card that open the retained run log in the pager) |
D |
Toggle prior-attempt view (only shown when the agent has retried) |
d |
Expand / collapse the agent header panel (sticky identity header above the detail panels) |
. |
Expand / collapse the jump panel (sticky jump targets below the detail panels) |
I |
Show/hide non-run agents |
V |
Open the focused agent's metadata as a sectioned document in the pager (see below) |
w |
Wait/unwait agent (opens WaitModal — see below) |
W |
Prepare a prompt that waits for the selected agent/session, clan, or named tribe; marks produce %w:a,b,c |
m |
Mark / unmark current agent, or all top-level agents in focused collapsed group (auto-advances to next) |
s |
Save and dismiss marked agents as a revivable group (opens optional group-name modal) |
U |
Toggle the focused agent's unread marker |
u |
Clear all agent marks |
x |
Kill / dismiss the agent, clan, or focused panel or group (or every marked agent); stop a monitor or proc |
X |
Open the cleanup panel for panel, all-panel, tribe, marked, group, or custom cleanup |
Enter |
Act on agent: review pending gate, go to Patch, or choose when several apply |
e |
Edit chat in editor; with marks, open all editable marked transcripts in one editor invocation |
E |
Edit focused deck content in editor (Files opens the real path; Main/Tools open the active card text as a temporary .md) |
t |
Open the focused agent's tmux target, or a workspace chooser (m marks many; a selector opens one) |
T |
Open tmux window in the agent's primary project workspace |
N |
Open the Tribe & Tab modal (tribe input is pre-seeded with pinned for agents without a tribe; empty keeps it, Ctrl+D clears it; on a clan row or clan member it sets the clan's recorded tribe; the second input moves the agent's whole presentation root between tabs — empty keeps, main or Ctrl+T clears; remote rows cannot move) |
z |
Start metadata fold mode for clan, agent node (session or single agent), or selected whole-tribe detail panels |
Z |
Zoom the focused deck panel in place (node column hidden); press again to restore |
= |
Isolate the focused tribe panel, or restore the remembered pre-isolation layout |
- |
Collapse every open agent-node/clan fold in the focused tribe panel, or restore the last sweep's folds |
_ |
Collapse every open agent-node/clan fold in every eligible tribe panel, or restore the last sweep's folds |
Ctrl+N / Ctrl+P |
Focused panel to the next / previous deck (Main → Files → Tools, wraps) |
p |
Pick the focused panel's deck: m Main, f Files, t Tools; M/F/T show it in the other panel (opening one below if needed); pp/Esc close |
When t opens the Tmux Workspace chooser, a displayed selector key opens that one
target immediately, even if other rows are already marked. m marks or unmarks the
highlighted row and advances to the next (wrapping around). Enter opens every marked
target in the chooser's displayed order, or only the highlighted row when nothing is
marked. q / Esc cancel without opening anything.
On Artifacts and Services, r still runs a Patch workflow or an Axe job/bgcmd, and R
still opens the Refresh panel (or refreshes immediately when that
panel is disabled). Only the Agents tab swaps those keys.
x: how a kill is carried out¶
x on a running agent hides its row at once and sends one SIGTERM from the TUI. The
rest runs in a durable sase agent persist-cleanup proc, which keeps going if you quit
the TUI. It publishes the dismissal first, then finds the agent's whole process set: the
runner's process group and session, its ppid descendants, and every process that
inherited the agent's launch scratch key (recorded as launch_scratch_key in
agent_meta.json), including ones that started their own session. It sends SIGTERM,
waits at least six seconds so a sase tool run wrapper can finish its own five-second
escalation, sends SIGKILL to whatever remains, and verifies every process is gone. Only
then does it release the workspace claim and delete workflow artifacts. A registered
proc or monitor supervisor found in the set is stopped through its own stop, so its
record settles. A dismissed row that is not success-terminal (for example a FAILED row
in retry backoff) is terminated the same way when its recorded runner is still alive.
If a process survives SIGKILL, the row stays dismissed, the workspace claim and
artifacts are kept, and an error toast names the surviving PIDs. sase agent kill NAME
runs the same termination in the foreground.
x on a running monitor, an active named proc, or a pending gate removes its row in the
same step. A running monitor goes through the ordinary kill confirmation and the durable
proc stops it before the other side effects. An active named proc or pending gate asks
for confirmation, then its row is hidden at once while the same durable bulk transaction
stops the named proc (through the native proc service) or cancels the gate. Clan, panel,
group, and marked cleanups include active named procs and pending gates the same way
instead of skipping them; any selected member the cleanup cannot cover is named in the
confirmation modal. A gate whose decision started executing under the cancel comes back
with an error toast. A gate that is already settling keeps the "waiting for a decision"
warning.
Enter: act on an agent¶
Enter on an Agents-tab row opens that agent node's pending gate (every gate kind,
including plan, question, sudo, launch, HITL, and custom gates), jumps to its Patch, or
— when more than one target applies — opens a one-keypress chooser. One target runs
directly; two or more open the chooser. Gates come first, newest first, with the Patch
last; the first row is the primary action, so Enter then Enter always opens the
newest pending gate. The footer names what Enter will do: the gate's action (for
example review plan), go to PR for a lone Patch, or choose action when several
targets apply.
What counts as a target depends on the selected row:
- A gate row targets its own gate while it is pending. A settled gate toasts
This gate already settled (<state>). - A session container collects every pending gate across its members, plus the
session's Patch (or, when the container has none, the Patch of its most recently
started member). A gate reachable through both a gate turn row and its inbox
notification counts once, so a session waiting on a single tale plan, with no Patch,
runs
Enterdirectly (footerreview tale plan); a session that also has a Patch shows thechoose actionfooter instead. - A session member or standalone agent targets gates it created and gate notifications matched to it. An agent stopped on a question, or a workflow step waiting for input, falls back to the answer flow when no gate notification matches.
- A workflow step child uses its parent workflow's Patch; a child of a project-level workflow uses that workflow's meta Patch, if it recorded one.
- A remote row answers its pending remote attention request.
- A clan container toasts
Select an agent inside this clan. Monitors and named procs have no targets, and a grouping banner ignoresEnter.
With no target, Enter toasts No pending gate or Patch for this agent. Enter works
from the first frame: if the notification inbox has not been polled yet, sase's TUI
reads it once off the UI thread and then acts. It searches every undismissed
notification rather than a bounded page, so older pending decisions stay reachable
regardless of backlog size. Before a gate opens, the target is revalidated; a gate that
settled in the meantime toasts Gate <id> is no longer pending.
The chooser is titled Act on <agent> and groups rows under GATE/GATES and PATCH
headings, each row showing its key, label, and a status badge (with age for gates). A
guidance line (⏎ again → <primary action> · or press a key) names the primary. Chooser
keys: one gate uses g; several gates use 1–9 in display order with g as a hidden
alias for the first; the Patch uses p. Enter selects the highlighted row (starting
on the primary), j/k or Up/Down move, Esc/q cancel, mouse click selects, and
other printable keys are swallowed. A choice whose target disappeared while the chooser
was open toasts That action is no longer available instead of acting.
The retired ,n leader chord is replaced by Enter. The direct Patch jump survives as
the jump_to_agent_patch command, unbound by default; see
ace.keymaps.
Forking Agents and Groups¶
With a named agent selected, press F to open a prompt prefilled with #fork:<agent>.
Selecting a session root uses the session name instead. Failed named rows are valid fork
targets; the child receives the failed parent's transcript plus the recorded failure
message when available. The same action works on the synthetic container row for a clan
(#fork:<clan>) and while an expanded or collapsed named tribe panel has whole-panel
focus (#fork:@<tribe>). The reserved @default panel and grouping banners are not
fork targets.
Finished planner rows (EPIC CREATED, PLAN COMMITTED) are also fork targets; a
session root forks its whole session, including the planner transcript and its
gate/monitor turns. PLAN REJECTED and STOPPED rows are not fork targets.
F also works on a stand-alone named-proc row and on a monitor session member, active
or settled. The prefilled reference is the proc's exact durable proc ID — not its
reusable friendly name — so the eventual fork can never drift onto a different proc if
that name is reused later; the footer and prompt label still show the friendly proc
name. A proc or monitor row has no chat, so it never advertises retry, edit-chat, or
name; x kills an active proc or stops a running monitor, and dismisses a settled
one.
Press W on the same selections to prepare %w:<agent-or-session>, %w:<clan>, or
%w:@<tribe>. A non-empty marked set takes precedence and produces one comma-separated
wait over the named marked rows instead of the focused group. The reserved @default
panel and grouping banners are not wait targets either.
Group references are dynamic; pressing F does not snapshot the selected transcripts. A
session reference contributes every known concrete turn in the session's sequential
chain, oldest first, agent turns and monitor turns alike, and includes turns that ended
unsuccessfully with their failure context rather than dropping them. Only turns that are
still running, or whose transcript/log is missing or unreadable, are listed as not
shown. Agent-turn members are transcripts of prior agents' conversations; named-proc and
monitor members are command execution records whose output is untrusted evidence, never
an instruction. An explicit --<suffix> reference contributes only that member. A clan
reference resolves the newest clan generation when the deferred launch proceeds and
requires every member of that generation to have succeeded. A tribe reference follows
the next-entity rule: the new run waits for the earliest successful entity in that tribe
launched after its own artifact was created. It does not fork the agents currently
visible in the selected tribe panel. See
Tribe wait and fork targets for the
full ordering rules.
sase's TUI also tries to carry VCS context into either prefilled prompt. For one
selected agent or session row, it uses that row's launch ref when it can resolve it. For
a selected clan or tribe, it adds a VCS tag only when every real agent in the current
scope resolves to the same workflow and ref. Mixed or missing context produces only the
#fork or %w reference, leaving you to add the desired #git, #gh, or other VCS
tag. A marked wait is different: one mark uses that row's context; multiple marks use
the selected marked row, or the first named mark when the selection is elsewhere. The
VCS lookup runs off the UI thread. Before opening the prompt, sase's TUI verifies that
the selected scope and its members did not change; marked waits instead verify the
marked target set. A stale selection cancels with a warning rather than opening a prompt
for the wrong target.
Clan and Session Detail Panels¶
Selecting a clan container shows a CLAN summary. Selecting a real multi-member session
root titles the sticky header panel SESSION (cyan, matching
the name), then shows the session's normal Main deck cards, with its SESSION TURNS
roster in the jump panel. A selected agent turn — standalone or session member — titles
the header panel AGENT TURN (gold, matching the name). Both rosters use the numbered
member jumps described above. Clan direct members in the Agents list sort by status
priority — Failed, Stopped, Running/Starting, Queued, Waiting, Done — with launch
recency breaking ties. The clan's CLAN MEMBERS jump-panel roster instead keeps
chronological launch order so its numbers do not change as statuses change; a nested
session remains one direct entry with its chain indented beneath it. Session rosters
retain sequential chain order.
Selecting a session turn row (not the container) also shows its SESSION TURNS
roster in the jump panel: the same enclosing session's members, in the same chain order,
minus the selected member itself. The heading carries a dim · <session name> suffix
naming the session, since the count shown is one less than the session's full size.
Unlike a container panel, a member panel folds this roster (and the rest of its own
sections) using the selected member's own three-level agent scale rather than the
session's two-level scale, so no Fold: N/M header line appears.
Clan metadata has three session-only detail levels. Session metadata uses the last two effective states as its two-level scale for fold-aware metadata: session level 1 is expanded and level 2 is fully expanded.
| Level | Content |
|---|---|
| 1 | Core member rows plus headings and counts; expensive disk-backed bodies remain deferred |
| 2 | Bounded triage detail such as activity, wait/retry state, context summaries, and compact member metadata |
| 3 | Full available sections and the richest member annotations, including workspace, timestamps, and attempt count |
The compact clan roster and its fixed numeric member jumps remain available at all three
levels. Clan sections appear only when their content is known to exist: known-empty
sections are omitted, while unknown required disk-backed content produces one dim
⋯ scanning member data… tail for the document instead of a placeholder for each
section. Session rosters and their numeric jumps likewise remain available at both
effective levels. Session macro and prompt sections are omitted when absent, while an
unfinished reply remains visible as pending rather than disappearing as empty.
AGENT PROMPT and the consolidated AGENT REPLY are plain navigation anchors whose
available conversation bodies stay fully visible at both session levels.
v annotates a clan document in place rather than replacing it: the panel keeps its
current sections and fold level and gains inline [N] markers. Clan hints come from the
clan summary, member-attributed ERRORS, variable, REPLIES, and PROMPTS bodies,
per-entry SASE CONTEXT rows, SLOW TOOL CALLS rows, and the COMMITS lane; each path
resolves against the workspace of the member that produced it, and summary paths that
name a plan, artifact, or delta resolve through an index computed during clan enrichment
rather than a blind workspace join. A logical plan: reference is marked and resolved
as one token including the prefix, and an archived prompts/<YYYYMM>/<name>.md
reference resolves into the project's agents sidecar checkout rather than the agent
workspace. Hints exist only where text is actually visible, so availability follows the
active fold level — level 1 hints the clan summary only, level 2 adds the bounded triage
lines, and level 3 adds full bodies and per-entry context, tool-call, and commit rows.
Markers are numbered in document order. The jump panel is hidden while hint mode is
active and returns with the same fixed member numbers when it ends. While clan
enrichment is still in flight the hint bar stays open and the document is re-annotated
when the deferred sections land.
The default fold chords are:
| Key | Action |
|---|---|
zz |
Cycle the whole Main deck document forward through its active scale |
zZ |
Open every fold to the active maximum; at that maximum, close every fold to the minimum |
za |
Cycle the foldable section, tribe CLAN SUMMARIES/PROMPTS entry, or session SASE CONTEXT lane at the top of the focused Main view |
zA |
Toggle that foldable section or entry between collapsed and fully expanded |
z1-z2 |
Set a session directly to level 1 or 2 |
z1-z3 |
Set a clan or single-agent scope directly to level 1-3 |
z1-z4 |
Set a selected whole tribe panel directly to level 1-4 |
The Fold: N/M header field reports the position within the active scale, while glyphs
on foldable headings show their effective per-section levels. Only session panels print
that header line; a single sase agent relies on the SLOW TOOL CALLS heading glyph
(and, with the jump panel expanded, the NEIGHBORS heading glyph) instead. On a session
conversation heading, za and zA refresh normally but do not create or change a
section override. A valid panel-level cycle, extreme toggle, or direct selection clears
real per-section overrides. Fold state is shared by the Agents detail documents: an
ordinary agent's own three-level scale shapes its NEIGHBORS and SLOW TOOL CALLS
sections, so z* chords have a visible effect on a regular sase agent, and the same
clan/agent scope carries over to the next selected clan or session container. Most other
sections on a regular-agent panel stay fold-inert, except the SASE CONTEXT / BEAD
lane's multi-line values: at scale position 1 (z1, Collapsed), a task or phase
worker's Notes, and a task worker's +1 Evidence, collapse to a one-line digest,
N lines (zz to show); single-line values never fold, and at positions 2-3 the full
value renders. A selected whole tribe panel adds level 4 for exhaustive detail. These
keys are configurable; see Agent Clans, Sessions, and Tribes for
the grouping model.
When sase's TUI knows a planner/author or epic lander's associated plan, the metadata
panel adds a PLAN lane in SASE CONTEXT. A task worker that authored a plan in the
same run also shows a PLAN lane beside its task BEAD lane. The lane order is PLAN,
BEAD, ARTIFACTS, the audited MEMORY, GLOSSARY, SKILLS, and WORKSPACES, with
absent lanes omitted once they have resolved. A plan or any recorded output is enough to
show the context section. An epic phase worker never shows its parent epic as a PLAN
lane. Instead, its launch metadata identifies the epic plan and exact phase bead, and
sase's TUI derives one phase-local BEAD lane from that phase's validated,
frontmatter-ordered entry. The lane shows Phase Title, Description, Size,
Epic Plan, and Epic Title; Size uses the literal xsmall, small, medium,
large, or xlarge label and the same accessible chip palette as epic summaries. The
phase title comes from the same validated entry, is normalized to one line, and wraps
losslessly like the other values. Authored descriptions are also normalized to one line;
a missing description uses the same stable plan-and-phase pointer generated during
deterministic bead creation. This modern path does not read the bead store, and missing,
unreadable, damaged, explicitly invalid, or out-of-range metadata keeps the known
identity/path fallbacks while rendering optional fields as unavailable, without
exposing the epic goal, dependencies, or any peer phase.
SASE CONTEXT streams: its lanes are resolved cheapest-first in batches and each
batch is published as it lands, so the section appears almost immediately instead of
waiting for the slowest lane. A lane that has not resolved yet is not the same as a lane
that resolved to nothing — while a lane is still in flight it renders a dim,
non-interactive resolving… row that holds its position in the order above, so the
section fills in rather than reshuffling as the remaining lanes arrive. PLAN and
BEAD share one backing lane, so both show their own resolving… row until it lands
and the panel can tell which of the two actually has content. Repaints coalesce through
the same debouncer that drives the rest of the header, leaving hint mode and scroll
position undisturbed.
Within SASE CONTEXT / ARTIFACTS / Beads, a selected agent's newest current structured
bead note appears beneath its bead row with its original author and append time. The
card shows at most three wrapped note-text lines, then points to the existing numbered
bead detail for the full note and any earlier notes. Edits retain the original author,
retracted notes disappear, and an audited read reason remains as a separate read:
continuation. The cached touch index supplies these previews off the interactive render
path; it never opens bead streams or resolves bead detail while navigating.
For planner/author and lander rows, the lane body contains the complete normalized
Title, Goal, and canonical Path; a tale additionally gets a Size row between
Goal and Path, showing the authored xsmall/small/medium chip, or the medium
chip plus a (default) marker when the tale's size was missing or an over-sized
legacy large/xlarge normalized at launch (see
Plan Frontmatter Schema and Validation);
epics never show a Size row here. The lane header shows the effective user-facing tier
(plan, tale, or epic) and, for epics, the phase count. The tier records how the
user approved the plan: approve means a plan approved without an SDD commit, tale
(and the legacy commit-only action) means a committed tale, and epic means a committed
or launched epic. That displayed choice survives a later commit or launch failure. When
action metadata is absent, sase's TUI falls back to a valid authored tier: tale or
tier: epic; a legacy committed record without a readable authored tier falls back to
tale, while a genuinely unresolved tier renders tier unavailable. Path selection is
independent: committed paths are relative to the agent workspace (including SDD sidecars
such as sase/repos/plans/...), while pending and explicitly uncommitted archives use
~/.sase/plans/....
Validated authored epics add a phase roadmap beneath those three rows. sase's TUI
validates this display as a launch consumer: modern phases retain their authored
xsmall, small, medium, large, or xlarge size, while historical phases with an
omitted size normalize to small; an explicit invalid size or other schema damage makes
all phase metadata unavailable. Each entry shows its one-based authored order and
diamond, title, fixed-width literal size chip, canonical ID, no dependencies or
after <id>, ..., plus an authored phase model when present. Optional descriptions get
their own hanging-indented line. The order and diamond glyph describe static plan
structure, not execution state or live bead progress. Tales retain the compact four-row
form (Title, Goal, Size, Path). The chip remains visible while the title and
other long ASCII or wide-Unicode values fold completely without ellipses; the lane caps
content at 80 terminal cells on wide panels and reflows to the normal deck panel or
zoomed deck width. Logical header text contains the same size labels for search, copy,
and style inspection. In hint mode only Path receives a numbered file hint, allocated
in the plan's visual reading order. Missing or damaged plans keep their known lane and
path visible; when epic context is known, validation failure renders one quiet
phases unavailable header state rather than partial phase data.
sase's TUI separates fast visible-inbox loads from full-history scans. The visible inbox
is the normal Agents-tab working set: every active row plus every non-hidden completed
row, subject only to a first-paint safety cap (about 6x the measured inbox). The first
load for a committed query reads that whole inbox as a baseline; refreshes after a
baseline stay windowed patches over it. Startup, manual This-tab refresh (r then r
on Agents), and active agent search use that path through the persistent artifact index
when it is available.
If the index is missing or unhealthy, sase's TUI falls back to a bounded source-artifact
scan for the first paint and shows a repair warning with the reason. That repair state
can arm a deferred full-history reconcile after input has been quiet, but normal Agents
This-tab refreshes still stay on the visible-inbox path. Use
sase agent index status --json for a lightweight check that does not scan source
artifacts, sase agent index verify to compare the index with source artifacts, and
sase agent index gc to rebuild the index and dismissed projection. Use r then f
(or ,y) when you want an immediate full-history refresh: it revalidates and reads the
whole archive through the artifact index, and falls back to a full source-artifact scan
only when the index is missing, busy, or fails. Normal SQLite deletes never reclaim disk
space; sase agent index vacuum reports freelist pages and dismissed row counts, and
-a/--apply compacts the index file with VACUUM (dry run by default; this never
removes or alters a row).
Refresh Panel¶
Press R on Artifacts or Services, or r on Agents, to open the Refresh panel: a
centered single-key chooser that names every refresh sase's TUI can perform, shows how
fresh each target already is, and runs exactly one of them.
| Key | Aliases | Option | What it does |
|---|---|---|---|
r |
R, 1 |
This tab | Reload the current tab's visible surface (Agents inbox, Artifacts, or Services). |
f |
2 |
Full history | Reload Agents from the complete archive. Works from any tab. |
u |
3 |
Usage windows | Re-probe provider subscription limits. |
a |
4 |
Everything | Forced sanity sweep plus full history and usage. |
R is an alias for This tab, so a double-tapped R R on Artifacts or Services (or
r r on Agents) reproduces the old immediate refresh. j/k (or arrows /
Ctrl+N/Ctrl+P) move the cursor; Enter activates the highlighted row (This tab when
the panel opens); Esc or q cancels. The ,y leader chord opens the same panel from
any tab with Full history highlighted and a ,y lives here now — press f reminder, so
,y then f (or Enter) runs the full-history reload.
Each row shows a freshness chip from a reload requested in this session (12s ago,
2h ago, just now). A surface that has not been reloaded yet shows —, never a
guessed age. The usage row shows checking… while it looks up how many providers it can
refresh. When auto-refresh is enabled, a header line reports its cadence and the
countdown to the next tick.
If usage metrics are disabled or no providers are eligible, the usage row stays visible
but unavailable: pressing u explains why and leaves the panel open so you can pick
something else.
The default-on refresh_panel sunset flag is the escape hatch back to the old gestures.
Disable it (sase flag disable refresh_panel) to restore immediate current-tab refresh
(R on Artifacts and Services, r on Agents) and ,y (Agents full-history) without
the chooser. See feature flags.
The dismissed projection that hides agents from the visible inbox is rebuilt from the
in-memory dismissed set unioned with every dismissed-bundle summary. Reviving an agent
now purges its dismissed bundle, so a revived agent stays visible. For archives that
accumulated stale bundles before that fix, plain sase agent index gc is not a
repair on its own -- it rebuilds the projection from those lingering bundles and
re-hides the revived agents. Run sase agent index gc --purge-revived-bundles (-r) to
first delete dismissed-bundle files and summary rows for suffixes that are no longer
present in dismissed_agents.json, then rebuild the corrected projection.
When one or more agents are marked, e edits the marked set instead of only the focused
row. sase's TUI opens editable completed transcripts in visible row order, deduplicates
repeated paths, skips live marked rows that are still running or have no chat file, and
reports that live skip count. Stale marks are ignored for this action, and marks remain
in place after the editor exits.
Sase Agent Neighbors Section¶
Every sase agent panel carries a numbered NEIGHBORS roster in the jump panel. The
section appears on session container panels after their SESSION TURNS roster when both
exist, and on ordinary agent panels. Tribe panel summaries, session member child rows,
and workflow aggregate rows have no NEIGHBORS section. Clan containers carry a
distinct CLAN NEIGHBORS roster instead (see below). A selected session turn row owns
no sase agent, so its panel carries only the SESSION TURNS roster (siblings, minus
itself) and never a NEIGHBORS section.
A selected clan container shows CLAN NEIGHBORS after its CLAN MEMBERS roster when
another clan shares its dotted hood. Two clans are neighbors when their presented clan
names share the same root hood: foo, foo.bar, foo.baz, and foo.bar.deep all
share the foo hood. Names compare case-insensitively at dot boundaries; the selected
clan itself, ordinary agent rows, malformed names, and unrelated prefixes such as
foobar are excluded. Each target keeps its full stable clan identity including its
generation, and rows render under their full clan names. The section appears only when
another eligible clan is visible under the current query and fold state. Numbering draws
from one continuous ladder shared with CLAN MEMBERS, so eleven combined entries use
two digits and entries beyond the shared 100 slots appear as an unnumbered
… +N more clan neighbors (not numbered) count. A digit jump reveals the target clan
through folds and tribe panels and participates in Ctrl+O jump-back history; a stale
relation or missing target cancels without moving selection.
The rows for that sase agent are ancestors, descendants including same-session dismissed
descendants, then hood neighbors grouped by hood, nearest hood first — under dim
ancestors, descendants, and <hood> hood group labels. A sase agent joins the hood
that matches its own name, and a session uses its bare session name for that match, so a
session visual.worker and a single agent visual.worker.notes relate as ancestor and
descendant exactly as two single agents with those names would. Row labels are shortened
relative to their group, so a myclan hood neighbor reads .code and a descendant
reads --impl.helper. A ⊘ glyph and a dismissed annotation mark dismissed rows, and
folded marks a prospective row that currently lives inside a collapsed clan. In the
jump panel the section sits after SESSION TURNS when both exist, so a sase agent's
numbered neighbors stay reachable without scrolling the Main deck body.
Every neighbor always renders and gets a digit whatever the fold level. The fold level
only changes the heading glyph and each row's annotation detail, both visible once the
jump panel is expanded with .. Numbering is stable across fold levels and JUMP-panel
toggles: the collapsed legend, the expanded roster, and the published jump map all share
one continuous ladder. The heading count is always the sase agent's total neighbor
count. The only hidden-row tail is the shared 100-slot numbering capacity, which reports
… +N more neighbors (not numbered). On a session, siblings that already appear under
SESSION TURNS are not repeated; they are reported by a dim
… +N also listed under SESSION TURNS tail instead. The heading count still includes
the suppressed rows.
Opened Repository Context¶
Configured linked_repos are recorded in agent metadata at launch time, while linked
and external repos opened during a run are recorded in opened-repository markers. For
non-terminal agents, sase's TUI can include dirty opened repos in the agent detail
SASE CONTEXT ARTIFACTS lane under Deltas. The field counts primary and opened-repo
changes together, groups linked and external entries under distinct glyphs and canonical
repo names, and resolves file hints relative to the opened repo directory. Missing
workspace directories, clean repos, and completed/failed agents are not part of this
live delta display.
When a SASE-launched agent uses /sase_repo, the run records an opened-repository
marker. The underlying command infers the host project and workspace from cwd;
configured linked repos remain backed by hidden PROJECT_STATE: sibling project
records, while external repos remain workspace-local and create no project record.
sase's TUI shows the markers in the prompt/detail SASE CONTEXT section with the repo
name, kind, resolved path, open time, and reason. Live deltas, commit diffs, and revert
all retain the canonical external name (for example, gh:pallets/click); reverting an
external repo discards local clone changes without re-cloning from the network.
Wait Modal¶
Press w on the Agents tab to open the WaitModal. It has five editable fields —
Agents, Beads, Time, Capacity, and Priority — each prefilled from
the agent's current wait, plus a focusable Follow epics toggle row directly under
Agents. Time, Capacity, Priority, and Beads render a live preview of how the typed value
will be interpreted; an invalid Time, Capacity, or Priority value blocks apply and
focuses the offending field.
The Follow epics row is a tri-state toggle: on (teal ↪), off (dim), or mixed
(each listed target keeps its own policy; newly added agents get the default). It joins
the Ctrl+J / Ctrl+K field cycle and Space toggles it when focused. When every
listed target is a --plan row the toggle is disabled with a reason, because --plan
rows release when the plan is submitted and never follow epics. Beads prefills with
authored beads only — beads a follow promotion derived are excluded. Applying the modal
writes the follow selection into the wait directive; removing a target, or turning its
follow off, drops its follow stage and its derived bead IDs while keeping the
already-pinned epic beads. Turning follow back on lets the next evaluation re-promote.
Beads completes against every non-closed bead in the agent's project, read from the same
canonical store the wait resolver consults, so a bead offered by the picker is always
one the resolver can see. The agent's own epic/phase bead is excluded from candidates —
an agent can never wait on the bead it exists to close. Candidates are ordered by status
(in_progress, claimed, ready, open, snoozed), then by most-recently-updated,
then by ID; the filter fragment matches bead ID or title. At most 100 rows render per
keystroke, with a trailing …N more — keep typing row when more match. A bead already
present in the field renders with a dim · selected suffix.
The Agents and Beads fields each have their own completion list directly beneath them, but only one is ever visible at a time — whichever of the two fields was focused most recently (Agents by default). Focusing Time, Capacity, or Priority never changes which list is shown, and the hidden list is not part of keyboard focus traversal.
The beads preview reports one of: an empty-field neutral message, a loading message
while the bead catalog loads in the background, a neutral "bead store unavailable" state
when the project's store can't be read (IDs are not verified in that case), a valid
summary of up to three typed beads with their status glyphs plus an aggregate count, or
an error when a typed ID isn't in the project's bead store or is the agent's own bead.
Applying an error-state bead wait is a soft, two-step guard rather than a hard block:
the first Enter focuses the Beads field and changes the footer instead of dismissing;
a second Enter applies the wait as typed. Any edit to the Beads field disarms the
guard. A store that can't be read never arms the guard, since bead stores sync through
git and a locally-missing ID can still be valid upstream.
Behavior depends on the agent's status:
- WAITING or QUEUED agent: Edit dependency names, bead gates, a time floor, the
per-launch
capacitybudget, or the runner-slotpriority. A runner-slot-parked agent applies a capacity- or priority-only edit live on its next poll; changing earlier wait stages restarts the agent. Clearing an explicit capacity budget returns that launch to the current globalmax_running_agentsbudget. - STARTING or RUNNING agent: Enter a dependency, bead gate, time floor, capacity
budget, or priority to kill and restart the current agent with canonical
%wait(...)/%queue(...)directives.
The Capacity field is this launch's capacity budget, replacing the current global
max_running_agents budget for its own admission decision. It is not a count of
individual turns. A serial session — including its monitor and --next follow-up —
still occupies one claim of its session weight, so that weight still counts against this
budget. Only a root or a live parallel clan member waits here; a serial session member
rides the session's slot and never parks.
Priority must be a non-negative integer and defaults to 10; lower values are admitted
first. See Runner slot waits for how priority
interacts with FIFO order and the bounded deference window applied to deprioritized
waiters.
Enter applies, Tab accepts a highlighted agent- or bead-name completion and
otherwise moves focus to the next field, Ctrl+R runs the agent now by clearing every
wait condition, and Escape cancels. The modal supports readline-style keybindings
(Ctrl+F/Ctrl+B/Ctrl+A/Ctrl+E) for cursor movement.
Ctrl+J and Ctrl+K walk forward and backward through the six fields directly, in the
displayed order (Agents, Follow epics, Beads, Time, Capacity, Priority), wrapping around
at either end and placing the cursor at the end of the field's current value. Unlike
Tab, they never consume a highlighted completion, so they move focus even while a
completion list is open; when focus is on the Agents or Beads completion list itself,
the step is taken from that list's own field. Up / Down and Ctrl+P / Ctrl+N move
within the visible completion list rather than between fields.
VCS Tag Resolution in Fork/Wait¶
When forking or waiting on an agent, VCS tags in the prompt (e.g., #git(ref),
#gh:ref) are automatically updated to point to the correct branch. For non-project
agents, the ref is replaced with the agent's PR name (branch). For project agents using
#pr, the ref is replaced with @<name> which resolves to the agent's branch. HITL
suffixes (!!, ??) are stripped during replacement since fork scenarios should not
carry over HITL overrides.
Workflow Visibility¶
Workflows launched via sase run are visible in the Agents tab alongside sase's
TUI-launched workflows. The TUI scans artifacts/run/* directories in addition to
workflow-* and ace-run directories, and writes an initial workflow_state.json
before execution so that step data appears immediately rather than showing a bare
RUNNING entry. Anonymous tmp_* workflows are included in the normal visible-inbox
index when their workflow state has appears_as_agent: true and does not set
hidden: true; explicitly hidden workflow rows are omitted from the default view.
Specialized review runners launched by axe (mentor, CRS, fix-hook, and summarize-hook
review agents) are also visible and are automatically grouped into tribe @review,
matching the behavior of a %id(..., tribe=review) prompt launch.
Agent Artifacts¶
Press a on a focused agent to open the artifact panel whenever artifacts are
associated with that agent. The list can include chat transcripts, plan files, generated
Markdown PDFs, generated images, generated videos, prompt-referenced media from saved
prompt artifacts, and explicit files saved with
sase artifact create -p <path> [-l <label>] [-k <kind>]. sase's TUI always opens the
panel, even for a single artifact, so the label, kind, and path are visible before
launching the terminal viewer. With agents marked, a opens one combined panel listing
every marked agent's artifacts, each row labeled with its agent's name.
The prompt/detail header includes those non-chat entries in the plan-adjacent
SASE CONTEXT ARTIFACTS lane. Within that lane, Beads, Reads, Commits,
Deltas, and Files stay in that order when present. Beads lists the beads the agent
touched, read, or viewed, plus its assigned beads (see the Main deck description below);
audited bead: reads appear there rather than under Reads. Reads lists audited
sase artifact read invocations (newest first, with reasons); prompt citations and
silent show / path / open inspection do not appear. Paths are made
workspace-relative when possible, and hint mode assigns numbers to filesystem-backed
reads and output paths so they can be opened with the normal file-hint flow.
Artifact panel controls:
| Key | Action |
|---|---|
| selector | Open the artifact with that one-key selector (1-0, then letters) |
j / k |
Move through artifact rows |
m |
Mark / unmark the highlighted artifact and advance to the next row |
% |
Open the file-kind Copy as… palette |
y |
Copy Markdown contents (an accelerator for the palette's c row) |
Y |
Copy the preferred anchored stored/source path |
Enter |
Open marked artifacts in list order, or the highlighted row if unmarked |
A |
Open all artifacts in list order, ignoring marks |
z |
Open marked artifacts (or the highlighted row) in a zoomed tmux pane; outside tmux it opens them normally with a warning |
q / Esc |
Close the panel |
The modal-local file palette offers @ prompt-form references, l Markdown links, c
Markdown contents, p stored paths, P source paths, J metadata JSON, and s
snapshots. Stored and source paths are separate, anchored answers; an absent source is
labeled “not recorded,” and copying a stale source keeps the “no longer exists” warning.
With marks, the palette copies rows in visible order: references and paths are
newline-separated, links form a Markdown list, metadata is a JSON array, and Markdown
contents use bounded fenced sections. Unavailable rows are skipped with an explicit
count. These modal-local l/J accelerators are distinct from the Artifacts Files
pane's compatibility-preserving %L/%j keys. The legacy y and Y accelerators
remain available and apply to the same marked set.
Y shares one helper with the Files pane, so both copy the same anchored
path: the stored path, except that PDF rows yield the live Markdown source they were
rendered from when the index recorded one. Relative index paths are anchored to the
producing workspace — including legacy rows whose workspace is discoverable only through
the agent's artifact metadata — and the completion toast says when the copied path no
longer exists.
When sase's TUI is running inside tmux, artifact viewing opens in a right-side tmux pane
so the TUI remains visible. The Agents list collapses while the tracked pane is live,
row-changing navigation shows a warning instead of moving to a different agent, l
focuses the tracked pane, and lowercase a closes it. If the pane was already closed,
lowercase a opens the artifact panel normally. Outside tmux, sase's TUI suspends while
the terminal viewer runs in the current pane. The viewer supports image, video,
Markdown, PDF, and text artifacts: images are displayed directly with kitten icat,
videos play with mpv, Markdown is first rendered to PDF, PDFs are converted to PNG
pages for paging, and unknown file artifacts fall back to a text viewer. The viewer
needs kitten for image/PDF/Markdown display, mpv for videos, pdftoppm for
PDF/Markdown paging, and pandoc plus a supported PDF engine for Markdown rendering.
Missing tools produce a warning instead of failing the TUI.
Viewer controls:
| Key | Action |
|---|---|
j |
Next page when the artifact has multiple pages; wraps around |
k |
Previous page when the artifact has multiple pages |
n |
Next artifact when viewing an artifact sequence |
p |
Previous artifact when viewing an artifact sequence |
r |
Refresh the current page |
z |
Toggle tmux zoom when available for the viewer pane |
Tab |
Focus the SASE TUI from a tmux artifact pane |
q |
Close the viewer |
Only one plan artifact is shown for an agent. When both an archived plan and an SDD tale path are present, sase's TUI prefers the committed SDD plan; otherwise it keeps the path that best matches the run metadata.
During successful-agent finalization, Markdown-to-PDF rendering updates
workflow_state.json.pdf_status and a compact activity label. sase's TUI renders that
label only in the prompt/detail header's labeled Activity: field, so long conversions
show progress such as PDF 2/4 <path> or PDFs done 3/4 (1 skipped) instead of looking
idle.
Tribe Side Panels¶
The Agents tab is laid out as a series of vertically-stacked side panels, one per
effective agent tribe. Agents without a stored tribe live in the reserved @default
panel; an explicit default assignment converges on the same panel, so the UI never
creates a duplicate default bucket. @default is derived for presentation and is not
backfilled into agent_meta.json or agent_tribes.json; clearing a user-managed tribe
returns the agent to this panel. Pressing N on a clan row or on any clan member
instead sets the whole clan generation's recorded tribe, and clearing it writes an
explicit unset that sticks even when members still carry an epic tribe (see
clan records). Every tribe renders as @<tribe> with a sase-agent
count in the panel title. One standalone agent or one sequential session is one sase
agent, and a rootless clan contributes one sase agent per direct member rather than one
for its synthetic container. Per-tribe icons, identity colors, and initial expansion are
configurable through ace.tribes; the special default
entry styles the reserved panel. Agents launched by AXE jobs land in the built-in @job
panel. SASE still stores that tribe under its legacy name chop, but @job wait and
fork targets, tribe:job filters, and an ace.tribes.job entry all address it (an
explicit ace.tribes.chop entry still wins for styling). A manual panel fold lasts for
that panel's current lifetime, and the configured initial state is applied again when
the panel appears after a restart or after the tribe disappears and returns. Once a
panel has shown rows under the current Agents query, it stays mounted for the session
even if a bounded or incomplete load briefly omits them; it retires only when every node
it showed is authoritatively gone — dismissed, killed, a dismissed named proc, moved to
another tribe, or absent from a complete history load — including a clan whose members
are all gone. Changing the Agents query starts that bookkeeping over. Across structured
sase's TUI surfaces, identity colors apply only to an existing configured icon and the
@tribe name; they do not recolor free-form @... text or selection, fold, count,
heading, and status chrome. Configured icons remain limited to surfaces that already
show an icon. Each panel title can also show compact scoped metrics in the form
[S1 R2 W1 F1 U1 D3]: S is stopped for human input, R is running, W is waiting to
start, F is failed, U is unread terminal work, and D is done/read terminal work.
Zero-count metrics are omitted. The status metrics use the same sase-agent projection as
the adjacent total and classify a sequential session once from its normalized owner
status. The selected whole-panel TRIBE header uses that same projection, while its
nested count and per-session/per-clan member summaries preserve the concrete-member
distinction. On the selected whole panel, the title marker, total, brackets, and metric
letters use the focus accent; each numeric metric count retains its semantic status
color. The title can end with an amber ⚙N badge for running monitors followed by a
grey ⚙N badge for finished ones, in that order, after the metric chip (or after the
total when the chip is empty); the two counts partition the tribe's monitors exactly.
Each badge is fold- and collapse-independent — it still reports on a fully collapsed
panel — and is omitted entirely when its own count is zero. Both badges keep their
semantic hue on a selected panel while the brackets and metric letters take the focus
accent. Panel heights are sized to their content and separated by a one-row gap. When
the panels fit, the first panel grows to absorb leftover vertical space while later
panels stay pinned to their natural height; when the panels overflow, space is weighted
by each panel's rendered row count.
A selected tribe panel's TRIBE identity fields (Name, Status, Composition,
Runtime, Fold) render in the sticky header panel above the
Main deck; collapsed, it shows the tribe name, status, and counts on one row and the
composition, runtime, and fold on the next. The scrolling Main deck opens with an
unlabeled description row only when the tribe has a configured
description. That row is set off by a blank line and
wrapped at a fixed 80-cell measure (no hanging indent — there is no label to indent
past). A tribe with no configured description renders no row there at all, including
unconfigured ad-hoc tribes with no ace.tribes entry. To find configured tribes that
are missing a description, run sase doctor -C config.tribes.
Use J / K to move across expanded panels (forward / reverse) and enter the first or
last selectable row in the destination; collapsed panels are skipped entirely, and the
keymaps do nothing when no other panel is expanded. Collapsed grouping banners count as
rows. When the focused panel's only selectable row is already selected — or it renders
none — lowercase j / k select the adjacent whole panel, wrapping across every panel
including collapsed ones; l or Esc then descends into the newly selected panel's
remembered row. This differs from J / K, which skip collapsed panels and land
directly on a row. Whole-panel focus is available only in the split layout. Lowercase
h walks from any agent or workflow-step row to its validated immediate workflow,
session, clan, and finally tribe parent without changing structural or grouping folds.
It also selects a lone split panel after the structural chain is exhausted. A selected
panel has a ❖ title and shows a fold-aware TRIBE summary in the Main deck. While it
is selected, j / k cycle whole panels without descending; l or Esc returns to
the remembered row. A second h collapses the selected panel when another panel remains
visible. On a collapsed panel, the first l expands it while keeping whole-panel focus
and the second returns to the remembered row; L on a collapsed panel does not expand
it — it warns Selected tribe panel is collapsed instead (see uppercase H/L below).
Lowercase h on a collapsed panel selects the visually bottom-most expanded panel
without changing any panel folds, and Ctrl+O returns to the collapsed origin. When
every live panel is collapsed, h remains a no-op and shows the existing
Panel is already collapsed warning. Apostrophe jump hints include every split-panel
title, even a lone expanded panel, as well as collapsed titles, and support the normal
Ctrl+O jump back.
Selection is the source of truth across refreshes: when a status change or roster reload moves the selected agent into another panel, panel focus moves with it. Focus stays put only when you parked it deliberately — whole-panel focus, a selected grouping banner, or a collapsed focused panel — or when the agent's new panel is collapsed, in which case the cursor returns to the focused panel instead. If the selected agent disappears, the cursor lands on the neighboring visible row.
Lowercase l only advances a real fold owned by the selected row or its immediate
workflow/session owner, so a visible hidden leaf under an already fully expanded
workflow is a no-op. Uppercase H is the structural mutation key. When the selected row
owns an open workflow or sequential-session agent node, the first press retreats that
agent node one fold level. From a visible hidden step that hides the selected row,
selection re-anchors to the agent-node owner. After the selected agent node is
collapsed, later presses fully collapse every remaining open agent node in the next
grouping scope, then only the open canonical clan enclosing the selected row. With that
now-collapsed clan container still selected, another press collapses every remaining
open canonical clan in the group; only a later press collapses the grouping banner. A
banner, already-collapsed agent node, or already-collapsed clan selection proceeds
directly to that remaining-agent-node or group-wide clan sweep. LLM Calls detail still
takes priority. On a selected expanded whole panel, H hints every currently expanded
agent node, clan, and top-level grouping banner in that panel — the same L hint
affordance restricted to collapsible targets — and fully collapses whichever one you
pick; it never expands and never touches the panel itself, which stays lowercase h's
job. A panel with nothing expanded warns without arming hint mode; an already collapsed
panel keeps the usual already-collapsed notification. The merged layout has no
whole-panel focus and keeps the row-focused group scope across the merged roster.
Press Z with a whole tribe panel selected to zoom that tribe's Summary card in place.
Press = to isolate the focused tribe panel: it keeps that panel expanded and collapses
every sibling panel. If that changes the layout, sase's TUI remembers the prior
collapsed-panel set for one session-local restore. Panels whose state would change back
show ↺ in their titles, the footer changes to = restore panels, and the next =
restores the remembered layout. A separate sibling-panel or layout mutation invalidates
the pending restore. An already isolated panel is an idempotent no-op and does not arm a
restore. = works from whole-panel focus and from a row selection inside a panel alike
— from a row, it isolates the panel that holds the cursor without changing the selected
row. This action preserves the selected panel's remembered row and is available only in
the split layout.
Press - to sweep every open structural fold — agent nodes and clans, never grouping
banners such as Done or Running — closed in the tribe panel that holds focus, in one
press. It resolves scope the same way = does: from whole-panel focus, from a row or
banner selection inside a panel, and in the merged layout, where it treats the merged
roster as one scope. - never collapses the panel itself; that stays lowercase h's
job, and it never touches an open grouping banner either — use H for that. When the
focused panel has nothing left to collapse, - reverses itself: it re-expands exactly
the folds its own last sweep in that panel closed, restoring each structural fold to the
level it held before (a fully expanded agent node comes back fully expanded, not merely
expanded). The restore is filtered at press time to folds that are still live in that
panel and still collapsed, so it is forgiving of folds the user re-expanded by hand or
owners that disappeared, and it never resurrects a fold that no longer exists. Each
panel remembers at most one sweep; a fresh sweep replaces that panel's record, and a
panel that stops being live drops it. While a restore is armed, the panel marks every
fold - would re-expand with a gold ▿ on the owner row and ▿N in the panel title;
those markers clear as soon as the next - press would sweep instead of restore. The
footer shows - collapse folds when the focused panel has an open agent-node or clan
fold to sweep, or - restore folds when nothing is left to collapse but a prior sweep's
reverse is still armed. A panel with only open grouping banners and no open agent node
or clan reports nothing to collapse or restore.
Press _ for the all-panels sibling of -: in one press it sweeps every open
agent-node and clan fold in every tribe panel, not just the focused one, and shares
-'s per-panel sweep records, so the two compose — sweeping one panel with - and the
rest with _ still restores everything in the reverse order. A panel that is
effectively collapsed is skipped in both directions, exactly like - refuses to sweep a
collapsed panel, though its record stays live and reachable once it is expanded again.
_ works identically from any selection — a row, a group banner, whole-panel focus on
an expanded panel, or whole-panel focus on a collapsed panel — sweeping or restoring the
other panels either way. When nothing is left to collapse anywhere, it re-expands every
fold every panel's last sweep closed, restoring each to its exact prior level. The
footer shows _ collapse all folds / _ restore all folds, but only once at least two
tribe panels are eligible; with a single panel it would be a redundant duplicate of -.
Per-panel actions (kill, dismiss, expand, etc.) operate on whichever panel currently
holds focus. Press X to open the cleanup panel: d dismisses completed agents in the
focused panel, D dismisses completed agents across loaded panels, k cleans the
focused panel, K cleans all loaded panels, m cleans marked agents, g cleans the
focused group, t opens the tribe chooser, and c opens the custom selector. Enter
runs the highlighted row, and Esc or q closes the panel. Each row is disabled when
the loaded roster contributes no candidates. Its detail line is a candidate count, not a
promise that every candidate can complete that action; the confirmation or planner may
narrow the final set.
There is no separate clan chooser. A synthetic clan container row is never a cleanup
target itself: every scope above expands it into the live members of its generation, so
members folded away behind a collapsed clan are still reached by a panel, tribe, group,
or marked cleanup. Loaded workflow children are pulled in when their parent is a
candidate, and identities are de-duplicated in Agents-tab order. Named-proc handling
depends on the chosen scope: panel/global and marked/group bulk actions can dismiss a
terminal named proc but skip an active one with a warning, while tribe-planner and
custom-selector cleanup omit named procs. To stop an active stand-alone named proc,
select its row in the Agents tab and press x. Every scope then continues through its
normal bulk-cleanup confirmation or planner flow.
The TRIBE summary has four metadata detail levels, controlled by the same zz, zZ,
za, and zA chords used for clan and session detail. From levels 1-3, zZ opens
every fold to level 4; at level 4, it closes every fold to level 1:
| Level | Name | Tribe summary content |
|---|---|---|
| 1 | Glance | Header, compact numbered top-level roster, attention previews, clan summary headline index (up to 8 clans), prompt headline digest (up to 8 distinct prompts), and headings/counts for non-empty sections |
| 2 | Triage | Bounded previews for every represented section; every clan summary (up to 24) with styled ledes, and every distinct prompt (up to 24) with labels, macro chips, and sizes |
| 3 | Inspect | Nested roster detail and grouped full section bodies, still with protective bounds; 16-line clan summary previews and 10-line prompt previews |
| 4 | Forensics | Unbounded bodies, tracebacks, the richest member annotations, full clan summary bodies (500-line safety cap per clan), full prompt bodies (500-line safety cap per prompt) and launch directives, and all-time runtime statistics and percentiles |
The compact roster and its fixed numeric jump targets exist at all four levels. Number keys jump to a top-level clan, session, workflow, or agent, expanding the required panel and ancestor folds first. These metadata-member numbers are separate from ordinary apostrophe entry hints, whose adaptive target keys may use two characters in a large list.
PROMPTS sits after CLAN SUMMARIES in the Main deck body (the TRIBE MEMBERS roster
itself lives in the jump panel): it maps what each agent in the tribe was asked to do.
Its number chips are the same digits as the roster jump targets. Identical prompt bodies
are listed once with a ×N badge and a shared-by list. za/zA on a prompt entry
opens just that prompt. It is the level-1 exception: unlike most sections, it shows
prompt headlines at Glance instead of only a heading. Entries also carry green macro
chips and, for multi-line prompts, a dim line count, separated by dim · dividers. When
the tribe's prompts target more than one known project, each entry ends with that
project's accent-colored +<project> chip (or its #<workflow>:<name> spelling when
the name is not tag-shaped); unknown targets and Patch refs get no chip.
CLAN SUMMARIES sits before PROMPTS in the Main deck body (the TRIBE MEMBERS roster
itself lives in the jump panel): it maps the curated summary of every clan in the tribe,
because clan intent reads before raw prompts. Each entry line carries the clan's roster
digit as a number chip, the clan label, a banner-kind kicker (EPIC, …) when present, a
headline truncated at 120 characters, and a line count for multi-line summaries.
za/zA on an entry opens just that clan's summary. Like PROMPTS, it is a level-1
exception: Glance shows the headline index, Triage adds a styled lede of up to four
lines, Inspect shows 16-line previews, and Forensics shows full bodies behind a 500-line
per-clan safety cap.
Reply, slow-call, prompt, and clan-summary presence enrichment is requested off-thread
at every tribe level so known-empty sections can remain absent. Full bodies still follow
the level-specific bounds above, and all-time runtime statistics remain level-4-only.
When required disk-backed content is not known yet, the document ends with one dim
⋯ scanning member data… tail; known-empty content produces no section or placeholder.
Section-level overrides inherit from the panel level and are cleared by a valid
panel-level cycle, zZ extreme toggle, or direct z1-z4 selection.
Tribes are set or cleared with N (see Agent Actions). When opening
the modal on an agent without a tribe the input is pre-seeded with pinned so a single
Enter promotes the agent into the standard "pinned" panel; that default makes tribe
removal discoverable too — opening the modal on an assigned agent and submitting an
empty string clears the tribe. The tribe=<name> keyword on %id assigns the tribe at
launch, and #tribe:<name> combines it with an automatic id; sase agent tribe manages
it from the CLI.
Group Banners and Folding¶
In STANDARD mode, agents within each tribe side panel use either a two-tier or
three-tier banner hierarchy depending on whether any agent in the panel targets a Patch:
- 3-level layout (panel contains at least one Patch-scoped agent): project → Patch
→ name-root. Project-scoped agents and agents with no
cl_namefall into a synthetic(no Patch)bucket that sorts last. - 2-level layout (no Patch anywhere in the panel): project → name-root.
Banners are rendered between agent rows and carry a summary chip
(N agents · K running · M failed). Workflow children inherit grouping identity from
their parent agent so banners never appear between a parent and its workflow steps.
Optional name-root and dotted-prefix banners appear only when they group at least two
rows.
Labels such as L0, L1, and L2 describe a banner's nesting depth, not a shared fold setting. Every emitted grouping banner has its own binary expanded/collapsed state, kept separately for each tribe panel and grouping mode. Three independent folding layers can therefore be visible at once:
| Layer | What it controls | Default keys |
|---|---|---|
| Grouping banner | Project, Patch, date, status, and name buckets | Repeated H collapses after scoped agent nodes/clans; l expands; - never sweeps banners |
| Structural row | Clan members, session members, and workflow descendants | H retreats a selected workflow/session one level, then remaining group agent nodes, then group clans; l expands; - sweeps every open agent node and clan at once |
| Split-panel title | A whole tribe panel; collapsing requires multiple panels | h or ' selects; h collapses; l expands; L hints an agent-node/clan/banner fold to toggle in the focused expanded panel |
| Key | Action |
|---|---|
l |
Expand the selected collapsed grouping banner or structural row; on whole-panel focus, expand or enter the panel |
h |
Navigate outward; collapse selected expanded panel; from collapsed panel, select the last expanded panel if one exists |
L |
From a row or the selected panel, hint every visible agent-node/clan/banner fold in the focused tribe to toggle; on a collapsed panel, show the already-collapsed warning |
H |
Collapse selected workflow/session one level, then remaining group agent nodes/clans/group, or hint a fold to collapse in the selected panel; compact expanded LLM Calls detail |
= |
Isolate the focused tribe panel, or restore the pre-isolation layout; works from whole-panel focus or a row selection |
- |
Sweep every open agent node and clan in the focused panel closed in one press, or restore the last sweep; never touches grouping banners or the panel itself |
_ |
Like -, but across every eligible tribe panel at once |
,H |
Collapse one fold by hint: the selected tribe's expanded folds from a row; every tribe's expanded folds and panel titles from a selected panel |
Collapsed grouping banners at any depth are selectable rows; expanded banners remain
visible headings but are skipped by row navigation. When a collapsed banner is focused,
l expands only that banner and moves focus to the next visible child banner or first
agent row. When a banner is focused, m toggles marks for all top-level agents in that
group; workflow child rows are not marked independently by the banner shortcut. x
performs a bulk kill/dismiss on every top-level agent in that group (single confirmation
modal). Marked collapsed banners show [✓] when all covered top-level agents are marked
and [~] when only some are marked. Marks take priority over the group for bulk
actions, so a non-empty mark set always drives the bulk action regardless of banner
focus. When a fold change hides the previously focused agent, focus snaps to the nearest
visible ancestor banner so navigation context is never lost.
Clan and session rows add an agent-tree hierarchy inside those grouping banners. Their
trailing names are color-coded by kind without an additional icon. A clan is a
selectable synthetic container, never an agent, and ends in an orchid <name> after its
rolled-up status and member counts. A real multi-member session root remains a teal
agent row and ends in an azure <name>; ordinary agent annotations and lone plan
proposers with only their display-only planner child remain gold. Clan @tribe labels
follow the orchid name. A clan row starts with the teal project label of its direct
members in the title slot, for example bob-cli (RUNNING) [R5 W3] research.35. A clan
spanning several projects shows the dominant project first, at most two labels, then
+N. The CLAN header repeats the label as a Project:/Projects: field and at the
start of the compact second line. A clan's outer fold is binary: from a collapsed clan
row, press l once to reveal its direct agents, session rows, and visible workflow
rows. The clan row's fold count and status chrome count those direct clan agent nodes
once; nested session or workflow members do not inflate them. To reveal descendants
within a session or workflow, move to that row and press l there; pressing l again
on the clan row itself has no effect. Lowercase h moves to the validated parent
without changing fold state. Sequential session members use --<suffix> names and run
one after another. Killing or dismissing a clan row cascades to the clan's live members;
acting on one member leaves its siblings alone. Direct clan members always sort by the
clan-local status priority Failed, Stopped, Running, Queued, Waiting, Done in every
grouping mode; Starting shares Running's rank. Launch recency orders only members in the
same status bucket. A session row moves as one unit with its follow-ups and workflow
steps, preserving their adjacency and internal order.
Clan rows aggregate member status using the same operational precedence: human-input
questions, pending plan review, failure, and running/starting states outrank queued
work; QUEUED then outranks WAITING, followed by an all-done result. Consequently, a
clan with queued work and ordinary waiters displays QUEUED unless a higher-priority
member state is present. When a clan has exactly one running member (STARTING
included) and no asking, reviewing, or failed member, the clan shows that member's exact
status: its label and styling, including the FINALIZING word and the RETRYING (Ns)
countdown, on both the clan row and the CLAN Status: line. For example, a session
running a TESTING monitor makes the clan read TESTING, while a failed monitor with
an authored stop label can read TESTED [W1 F1 D9] and still remain in the Failed
group. The status bucket, precedence, and count chip are unchanged; TESTED is only the
authored turn label, not proof that verification passed. When the aggregate is QUEUED
and exactly one direct member is queued, the clan row and CLAN Status: line also show
that member's admission rank (QUEUED #3/4), plus the same pN / held by extras the
member row shows. Two queued members stay generic QUEUED with no rank. The count chip
remains concrete and independent, so QUEUED #3/4 [Q1 D4] keeps the rank next to the
chip, and QUEUED [Q3 W6] reports three runner-slot waiters and six dependency, bead,
or time waiters without merging the two categories.
A clan row whose waiting members name unknown targets attaches an orange ?N
immediately after the waiting count inside its count chip, as in QUEUED [Q1 W3?2]. N
counts distinct unknown agents, clan members, and beads across the clan's direct
WAITING members, so a dependency shared by two members counts once. Expand the clan to
see which member's WAITING ?N token names the stale target.
The uppercase H ladder starts with the selected workflow or sequential-session agent
node when that agent node is still open. The first press retreats that agent node by
exactly one fold level. From a visible hidden step, that press hides hidden steps while
leaving ordinary descendants visible and re-anchors selection to the agent-node owner;
the next press then collapses that still-selected agent node. Clans stay binary and do
not take this two-level path. After the selected agent node is collapsed, later presses
continue through the existing group-scoped remaining-agent-node, selected-clan,
remaining-clans, structural-fallback, and grouping-banner ladder. If the grouping banner
that H would collapse next contains any open standalone workflow, agent, or
sequential-session sase agent, and the selection does not own an open workflow or
session fold, the next press drives every such remaining agent node directly to fully
collapsed while leaving the banner open. Once remaining agent nodes are saturated, a
selection inside an open canonical clan makes the next press collapse only that clan. A
selected descendant re-anchors to its visible clan container; selecting the container
itself preserves selection without writing new selection memory. With the collapsed
container still selected, the following press drives every remaining open canonical clan
in the group directly to collapsed. A grouping banner, already-collapsed agent node,
already-collapsed clan, or invalid clan owner falls through to that group-wide sweep
immediately. The footer advertises H collapse workflow or H collapse session while
the selected agent node is open, then H collapse sase agents, then H collapse clan,
then H collapse clans, and only then H collapse group. Equal group names in other
tribe panels are never affected; merged layout intentionally treats the merged panel as
one scope. Ambiguous or malformed clan owners are skipped without blocking valid
siblings.
Whole-panel focus gives H a hinted collapse instead of the group-scoped ladder,
because it has no selected row or grouping scope to walk. It enumerates every currently
visible expanded agent node, clan, and top-level grouping banner in the selected panel —
never an owner hidden behind a still-collapsed parent banner, since that owner isn't
emitted as a row at all until its parent is expanded — assigns each one an adaptive hint
key, and shows the chips in place of jump hints. Typing a hint fully collapses that one
fold; an already collapsed fold is never offered, so every hint does something. H
never expands and never collapses the panel itself, which stays lowercase h's job. A
panel with no expanded folds warns without arming hint mode; an already collapsed panel
keeps the existing Panel is already collapsed warning. The footer shows the configured
hooks_or_collapse_all key as collapse fold whenever the selected panel has an
expanded agent node, clan, or top-level banner to hint. L's hint mode uses the same
enumeration but is not restricted to collapsible targets, so it also offers currently
collapsed agent nodes, clans, and banners and toggles whichever one you pick.
The ,H leader chord is always one scope wider than the selection. From a row or banner
it opens the same collapse-only hints as whole-panel H for the focused tribe. From a
selected tribe panel — expanded or collapsed — it hints every expanded fold in every
expanded tribe panel and adds a title chip on each of those panels, so one keystroke can
collapse a different panel without navigating to it. Whole-panel focus stays on the
panel you started from, so ,, repeats the picker against the current selection. The
footer reads COLLAPSE · ALL TRIBES in that wider scope. ,H never expands and never
routes through LLM Calls compaction.
Visual treatment: every row carries a fixed-width tier-guide gutter built from one │
segment per ancestor L0/L1 banner (in the parent tier's dim accent — project blue or
Patch cooler accent), so nesting reads as a tree at a glance. L0 project / bucket
banners use a sky-blue ▌ left bar and a heavy ━ rule. Patch banners, BY_DATE
subgroups, and banners that own another dotted-prefix subgroup use a cooler ▎ bar and
lighter ─ rule. Leaf name-root and dotted-prefix banners use a ▸ branch glyph with a
teal label. Singleton name-root groups suppress their banner entirely to reduce visual
noise.
The currently-focused side-panel row is marked with a thick accent-colored left bar, bold text, and a translucent accent tint applied to the row background. The tint is intentionally light so per-token status colors (running cyan, failed red, waiting yellow, etc.) remain readable through the highlight — the bar and bold weight do most of the work of marking the selection.
After a kill or dismiss, focus re-anchors on the visually-next row (rather than the next row in input order) so the selection always lands somewhere meaningful in the rendered tree.
Grouping Modes¶
Press o on the Agents tab to open the grouping picker, then choose p Project, d
Date, s Status, or m Machine. The picker's layout row is a three-level ladder —
Split by tribe, Merged, All tabs — walked with o (next) and O (previous,
wrapping); h/l and the arrow keys move the highlight while the row is focused, and
the picker closes after a step. oo steps forward one rung, oO steps back. With fewer
than two agent tabs only Split by tribe / Merged are offered. Split shows one panel
per tribe titled @tribe · N; Merged shows one panel titled with the tab's label and
count (All agents while the tab strip is hidden); All tabs shows every tab in one
panel titled All agents · every tab · N, with a tab chip on named-tab rows and
panels: all tabs in the info row. Merged panels annotate named tribes inline on agent
nodes; the default tribe is implied there, so default nodes do not add @default
labels. Transitions keep the selected node: zooming in from All tabs lands on that
node's tab, and choosing a tab at the All-tabs level drills into it. Panel layout and
grouping mode are independent: stepping the ladder preserves the active grouping mode
and does not persist beyond the current session. The Agents tab shows a brief toast
(Grouping: by project / by date / by status / by machine) when the chosen mode
changes:
| Mode | L0 buckets | Notes |
|---|---|---|
STANDARD |
Project (with optional Patch sub-level) | The "by project" default. Uses the 2-/3-level layout described above. |
BY_DATE |
Today / Yesterday / This Week / Earlier |
Date bucket at L0, then a date-aware L1 subgroup. Sorted newest-first within each bucket. |
BY_STATUS |
Stopped / Failed / Running / Queued / Waiting / Done / Starting |
Bucketed by shared status semantics; status priority fixes bucket position. Standalone agent nodes precede name subgroups, with launch recency sorting units inside each partition. |
BY_MACHINE |
here, then remote aliases alphabetically |
Machine at L0, status at L1, then name-root/name-prefix groups. Project and Patch levels are omitted. |
In BY_DATE mode, sase's TUI chooses one L1 subgroup style from the L0 date bucket:
one-hour windows (09:00) for Today and Yesterday, calendar-day labels for
This Week, and Monday-start week ranges for Earlier. The time anchor is stop_time
for terminal agents and start_time otherwise. The same anchor selects the L0 date
bucket, so an agent that started Friday evening and finished Saturday morning renders
under Saturday's bucket, matching the finish timestamp on its row. Buckets and their
subgroups sort newest-first. Workflow children inherit the parent's anchor so they stay
adjacent regardless of their own start time, and agents with no usable timestamp fall
into a (no time) subgroup that sorts last.
In BY_STATUS mode the L0 banner is the status bucket and L1 is the name-root, with the
same singleton-suppression rule as STANDARD. Status priority fixes the bucket order:
Stopped, Failed, Running, Queued, Waiting, Done, Starting. Within each bucket,
standalone sase agents render before every visible name-root subgroup; start_time
sorts agent nodes newest-first inside the standalone partition and subgroup units
newest-first inside the subgroup partition. The same partitioning rule applies under a
name-root, where directly contained agent nodes precede visible dotted-prefix subgroups.
Units with no launch timestamp sort after timestamped units within their partition, with
structural names and input order providing deterministic tie-breakers. A session, clan,
or workflow subtree uses its outer/root agent's launch time and remains contiguous.
Inside a clan, direct members still use the clan-local Failed, Stopped,
Running/Starting, Queued, Waiting, Done priority described above, with launch recency
breaking same-status ties; that order intentionally differs from this L0 bucket order.
Session follow-ups and workflow steps remain adjacent to their direct-member anchor in
their established internal preorder, including any name-prefix banners. The Starting
bucket remains last and its transient rows remain hidden, so startup-only work does not
displace active rows during daemon or launch refreshes. Each mode keeps its own
per-group fold registry, so collapsing buckets in BY_STATUS doesn't affect the project
layout you had in STANDARD. BY_STATUS banners are prefixed with semantic glyphs
(▲, ✗, ▶, …, ⏳, ✓, ◐) so the bucket title still leads visually.
In BY_MACHINE mode, local rows use the here L0 bucket and remote rows use their
enrolled alias. Every machine is divided into the same priority-ordered status groups as
BY_STATUS, and every status banner remains visible even for a singleton because it
communicates lifecycle state. Within a status group, standalone sase agents precede
name-root/name-prefix groups and launch recency supplies the same deterministic
ordering. A status change can therefore move a row between subgroups while keeping it
under the same machine. As in the other nonstandard modes, project and Patch grouping
levels disappear and this mode keeps an independent fold registry.
The active grouping strategy is also surfaced in the Agents tab header via an
unbracketed group: <label> (o) element so the current session mode is always visible
after the cycle toast fades. Top-level header elements are joined by a dim ·
separator: the leading counts group, an optional filter: element, an optional view:
element, the always-visible group: element, and an optional refresh: <N>s (r)
countdown. Only the agent status counts and the filter's match count keep square
brackets: an active filter reads filter: <query> [matched/loaded] (/), where the dim
trailing (/) names the configured edit_query key. After the first scan, the header
starts with the visible sase-agent total N. One standalone agent or one sequential
session is one sase agent, regardless of whether the session is folded. A rootless clan
container contributes no sase agent itself; each direct clan member contributes one, and
a direct member that is a sequential session still contributes only one. A hidden
top-level STARTING agent contributes one sase agent even though it is not selectable
yet. Grouping mode, tribe ownership, and fold state do not change this projection.
Runner load lives at the right of the row as a labeled load: <load>/<capacity> gauge,
just before the model/project cluster, followed by a · separator. <load> is occupied
runner capacity units and <capacity> is the current effective max_running_agents
budget (temporary override first, configured value second). Normal agents claim 1.0;
non-default %queue(weight=...) / %q(w=...) launches claim their authored capacity
units, and a %q(w=0) launch claims none. Both numbers render as integers when possible
(7/10, not 7.0/10.0), trimming to 2 decimals otherwise. If the capacity snapshot is
unavailable, sase's TUI renders —/— instead of deriving a fake value from visible
rows; when only the occupied value is unknown it renders —/<cap>. Capacity belongs to
the machine running this sase's TUI session and does not change when the Agents list is
searched, folded, filtered by tribe/project, or focused on remote rows. Rows from other
enrolled machines never add to this machine's load, although they still show their own
machine's capacity and weight badges (described under Queued below). The visible
running count and the global queued count remain in the status strip, for example
8 [8 running · 1 queued] with load: 8/10 at the right. The gauge shares the
usage-window ten-step color gradient, keyed on free-capacity percent, so a given color
means the same headroom in both places; at or over capacity it becomes the inverted red
chip, exactly as an exhausted 0% usage window. In compact density the load: label
drops, leaving 8/10. Hovering the gauge spells out the units in use and free, or that
new agents queue until capacity frees up when it is full. The tooltip also lists each
claim holder and its weight, and a session's running monitor holds its session's claim
even when the session row is under Done (see Runner slots).
The running count keeps its stable green count style. A nonzero queue count is
cornflower blue.
An optional status strip follows in the form
[S stopped · T starting · R running · W waiting · F failed · U unread · D done], with
numeric counts in place of the letters and zero-count metrics omitted. These buckets
classify the same sase agents as the leading total, using a sequential session's
normalized owner status instead of counting historical members separately. stopped
counts agents paused for plan approval, questions, or workflow human-input steps;
starting counts just-launched agents that have not yet surfaced as visible rows;
running excludes queued, waiting, failed, and stopped agents; waiting contains
genuinely blocked dependency, bead, and time waits, while the status strip's queued
count contains every live runner-capacity waiter; failed is terminal failed work;
unread counts terminal sase agents that still need acknowledgement; and done is
completed visible work that has already been acknowledged. Nested session/clan member
summaries remain concrete. The position/navigation denominator is a separate count:
rendered selectable roots, where a clan container is one row and a hidden STARTING
agent is excluded. During startup the header renders Agents: … until the first agent
scan has loaded, avoiding a misleading zero-agent count. Each TUI launch starts in
by-project grouping; cycling only changes the current session.
Queued holds QUEUED agents that have cleared every dependency, bead, and time wait
and need only runner capacity under their current admission budget. A queued row renders
as QUEUED #3/12, followed by pN for an explicit queue priority and by
held by <armer> while an agent hold keeps it from starting. A
sequential session whose next member is queued shows that member's QUEUED status and
queue position on the session row. A clan with exactly one queued direct member does the
same on the clan row and CLAN Status: line; two queued members stay generic QUEUED
with no rank. Authored capacity renders as a quiet cN badge beside the existing wN
weight badge, and turns gold when the launch's budget exceeds the current effective
global limit. Non-default queue weights render as the same quiet wN badge used on
running rows and queue-ladder entries; an explicit zero renders w0. The detail pane
repeats these values as Weight: and Capacity: lines. Remote rows show the same
badges and detail lines using the values reported by the machine that owns the agent.
New prompts reject capacity=0, but a persisted legacy record with an explicit zero
capacity still renders c0 and Capacity: legacy 0 (exact-weight drain budget).
Waiting holds genuinely blocked but self-progressing agents — WAITING with a time
wait (%wait(time=5m), %wait(time=1430)), a non-empty waiting_for dependency, or a
bead wait. A compact WAITING row summarizes named waits as one sequence of independent
tokens: agent counts keep the established status glyphs (✗1 ▶1 ✓1 ?1), while bead
counts keep the canonical Beads-tab status glyph (○ open, ◐ in progress, ●
closed). When a bead status matches a present agent bucket, the bead token follows that
agent token, for example WAITING ▶1 ◐2 or WAITING ✓1 ●1; unmatched bead tokens trail
in canonical bead order. Zero entries are omitted. When a row waits on exactly one bead
and has no agent, name, or group wait dependency, sase's TUI shows that bead's ID
instead of a count. The ID has no prefix until status resolution; afterward it is
prefixed by the bead's status glyph (or ? for an unknown status). Multiple or mixed
waits keep the counts. Unknown agents and unknown beads both render as ?N; when both
are present they appear as adjacent independent counts, as in WAITING ?1 ?2. These
tokens sit directly after WAITING and before a reserved-tribe !, duration, or
countdown annotation. A %wait(for_epic=) follow appends teal ↪ tokens after the
counts: WAITING ↪ epic… while the epic launch is in flight, WAITING ↪ ◐ sase-7k
while following one epic, WAITING ↪ ◐2 while following several epics, and
WAITING ↪ ! when the follow is blocked. An armed target with no recorded follow stage
adds no row noise. They are not the trailing gold ◆ linked-bead badge that marks an
agent launched by sase bead work. Stopped keeps the strict "you need to act"
semantics for plan approval, questions, and workflow input.
Agent Row Glyphs¶
To keep rows compact, agent statuses and types are rendered as one- or two-character badges instead of verbose text:
| Glyph | Meaning |
|---|---|
▶ |
RUNNING |
✓ |
DONE |
✓P |
PLAN DONE |
▶P |
PLAN APPROVED |
★E |
EPIC CREATED |
✎ |
PLAN |
✗ |
FAILED |
… |
QUEUED |
⏳ |
WAITING |
? |
QUESTION |
↻ |
RETRYING (followed by attempt count, e.g. ↻2) |
⊛ |
Finalizer chip: the most severe finalizer state since the newest successful run (failed > refused > interrupted > deferred > running), with k of n runs when several runs are in play; the row word reads FINALIZING while finalizers run (see Finalizers on the Agents tab) |
≡ |
Workflow row (top-level) |
❑ |
Patch / Patch row (top-level) |
⚡ |
Autonomous (%auto) agent |
◌ |
Hidden agent (visible only when . toggles them in) |
⚙ |
Monitor turn (row label) |
⚙N |
N running monitors in a session/clan subtree, or in a tribe panel title for its whole tribe (amber) |
⚙N |
N finished monitors in a session/clan subtree, or in a tribe panel title for its whole tribe (grey) |
⋔ |
Gate turn; its gate accent while pending/running, grey when settled, red on failure |
⋔N |
N gates in a session/clan subtree or tribe panel title, colored by lifecycle bucket |
▣ |
Stand-alone %proc named proc (row label; beta, typed_launch_units) |
▣N |
N stand-alone named procs in a panel title's separate proc chip |
A monitor turn (a session member whose work is a supervised command, started with
sase monitor start) renders an amber ⚙ glyph and omits a left-side title — identity
is the right-hand %id (<session>--mon), not the configured monitor label. A live
elapsed suffix or exit-code/timeout badge replaces the ordinary agent statuses. The
status token — the start label while running, the stop label once settled — is colored
by a deterministic accent derived from that pair, so every TESTING/TESTED monitor is
the same hue; failed, timed-out, and lost monitors stay red. Two extra badges mark a
stalled monitor handoff: a red ⚠ replaces the exit-code badge when a terminal
monitor's supervisor never reported a real exit code, and an amber ⚑ follows the row
when its --next follow-up was dropped or launched degraded — a monitor can finish
cleanly and still strand its follow-up. See Monitors.
Gate turns use ⋔ and their authored pending/settled labels, such as GATE/GATED,
QUESTION/ANSWERED, or plan-tier statuses. A pending gate has no LLM process and does
not occupy a runner slot, but it may retain the session's workspace claim. Its state is
one of pending, settling, answered, completed, failed, timeout, stopped, or lost.
Session and tribe chips count pending/running, settled, and failed gates separately by
color. After a decision, the gate member owns the displayed outcome: handoff outcomes
PLAN APPROVED, TALE APPROVED, EPIC APPROVED, and ANSWERED use the Running bucket
even though the gate itself has settled, so the row represents the successor handoff.
Rejection and cancellation use Done; timeout, failure, and lost gates use Failed.
A monitor row nests under the agent that started it, not under a synthetic aggregate —
one gear-glyph row at the starter's depth plus one. It is revealed by its agent
session's fold rather than its starter's own: a collapsed session shows an amber ⚙N
badge for its running monitors and a grey ⚙N badge for its finished ones — the two
counts partition the subtree's monitors, with a monitor that has not reported a terminal
state counting as running — and counts every monitor in its collapsed ×N, but renders
no monitor row, even when the session root itself is the starter. The tribe panel title
aggregates both lanes across the whole tribe, so a fully collapsed panel still reports
both what is still running and how much has already completed inside it. A single l on
the session container row reveals every member and monitor in that session in one step;
monitors are not deferred to a further "fully expanded" press the way hidden workflow
steps are. Selecting a monitor row and pressing l or H acts on that governing
session fold — H collapses the session and reanchors the cursor there — while h
still walks up to the monitor's starter.
A monitor has no LLM process to kill, so x on a selected running monitor row is
routed off the ordinary kill/dismiss path: it opens a Stop Monitor confirmation
(defaulting to Keep running) and, once confirmed, terminates the supervised command
through the same code path as sase monitor stop. As with the CLI, stopping never
launches the recorded --next follow-up agent. The stop itself runs as a tracked proc,
so a slow teardown does not block the TUI. x on a monitor row that has already settled
falls through to the normal dismiss behavior, and bulk scopes still win over the single
row: marks, a focused panel, and a focused group are all handled before sase's TUI looks
at whether the selected row is a monitor.
Agents launched by sase bead work also show a gold ◆ <bead_id> badge between the
status glyph and the tribe/name. That trailing gold ◆ linked-bead badge is a
fixed-style identity marker, not a wait-count token; waited-on beads in a WAITING
summary use status-colored ○ / ◐ / ● tokens instead. A phase agent named
<epic_id>.<N> displays that phase bead ID; the final <epic_id>.land agent displays
the parent epic bead ID; a standalone task worker named <task_id> displays its task
bead ID. Legacy plain <epic_id> land agents keep the same badge. Legacy dismissed
names keep the badge after their historical date prefix is stripped. Modern phase and
task rows use their explicit launch metadata immediately; legacy bead-shaped names
retain the deferred bead-store confirmation fallback.
Each agent row also carries a per-provider emoji badge before the display name so the LLM provider behind a row is readable at a glance without scanning the right-hand model suffix:
| Badge | Provider |
|---|---|
| 🎭 | Claude |
| 🪐 | Antigravity (agy) |
| 🤖 | Codex |
| 🐼 | Qwen |
| 🐙 | OpenCode |
| 🦋 | Muse Code (Meta) |
| 🚀 | Grok Build (xAI) |
The same provider palette also colors the <PROVIDER>(<model>) suffix on the right edge
of the row — the provider name, the parentheses, and the model name each render in a
distinct shade from that provider's palette so multi-model fan-outs are easy to scan.
Providers without a dedicated palette (anything outside the table above) fall back to a
neutral purple palette and render no emoji badge.
An agent that was materialized on this machine by the retired agents-sync import leg
carries an owner badge — [<label>] after its identity name — recording where it
came from. A foreign machine belonging to you renders as just the machine name; another
user's run renders as username@machine. The badge appears wherever an imported
identity is named: agent rows, the revive modal, and neighbor pickers. Imported
sequential sessions are also folded under one synthetic session-container row so members
imported without their root still group as one session instead of scattering across the
panel. Both are presentation over leftover local state; nothing imports new rows any
more, and sase agent names purge-local-state removes the state that produces them (see
Agent Hood Synchronization).
Workflow child rows for python and bash steps render a leading ❯ glyph plus the
step name, styled with the matching step-type accent — bash amber, python green —
because that name is those rows' identity. The glyph's presence is a stronger signal
than the step-type color alone for colorblind users and for rapid scanning; color still
carries the bash/python distinction. Sase turns (session-member agent turns, monitor
named procs, and workflow agent steps) omit a left-side title; their identity is the
right-hand %id / session-member name. Parallel rows fan out into structural children,
and prompt_part rows are invisible by default.
The right-hand edge of each row carries a runtime suffix
(<start-timestamp> · <elapsed>) right-aligned within the panel. Active rows that have
actually started include a 🏃♂️ marker before the ticking elapsed duration; unread
completed rows use a ✅ marker in the same suffix slot, or ❌ when the agent
finished in a FAILED state; and user-paused rows (PLAN, QUESTION, WAITING INPUT)
use a ✋ marker while waiting for a human response. Pre-run WAITING and QUEUED
rows with no BEGIN time hide the suffix so admission waits do not look like live
runtime. On an active sequential-session container, the live suffix is
🏃♂️ <current-turn-runtime> / <session-total-runtime>: the left value is the concrete
agent or monitor turn currently executing, and the right value is the aggregate session
interval. Gate-turn windows are excluded from that session total — they measure the
human decision, not agent runtime — and the same exclusion applies to clan totals. On an
active clan container the live suffix is
<lowest-running-lane-runtime> / <clan-total-runtime> (same marker), where the left
value is the smallest current runtime among the clan's running lanes (a sequential
session lane contributes its total runtime, the same value its own row shows on the
right of its suffix) and the right value is the aggregate clan interval. For finished
agents, the start-timestamp half is rendered as a humanized (date_prefix, time) pair
sized to fit the existing 15-cell slot:
- Same day:
HH:MM:SS - Prior day, same year:
Mon DD HH:MM(drops seconds — they're noise once a row finished hours ago) - Different year:
Mon DD 'YY(date only)
The elapsed duration starts at BEGIN when a row recorded wait-before-run metadata,
otherwise at the row start time. For slot-participating user agents, BEGIN is runner
admission and includes primary and linked-workspace preparation in the active runtime.
Completed DONE / PLAN DONE / TALE DONE workflow rows use the terminal agent stop
time when one exists; plan-step rows that finish without a subprocess stop time anchor
to the latest recorded plan submission time so completed planning rows do not keep
ticking. PLAN APPROVED rows with a running follow-up show active elapsed time for the
planner segment plus the coder segment, excluding the idle approval gap between plan
submission and code launch. The date prefix uses a softer dim #8787AF while the time
half keeps the standard #8787AF, giving the column internal hierarchy without
inflating the palette. Statuses not in the table fall back to (STATUS) text for
forwards compatibility.
A stand-alone %proc unit (beta, behind typed_launch_units) is backed only by the
proc store — it is never an agent, never nested under an agent session, and never
counted in agent runner, unread, clan, or session totals. It renders as its own
top-level ▣ row with a Bash/Python language badge, current phase/status, elapsed time,
and project, and a panel title reports it in a separate ▣<count> chip alongside the
ordinary agent metrics. Selecting one opens a NAMED PROC detail (status/phase
timeline, project/ workspace/cwd, language, code digest and safe preview, waits,
condition result, timeouts, and a bounded live-log tail). x on a running stand-alone
named proc asks for confirmation and then kills it and removes its row in one step — the
durable cleanup proc stops it through the native proc service — and x dismisses a
finished one with no confirmation. Dismissal only clears the Agents-tab row — the proc
stays visible in the Procs pane and in sase proc show, and a
dismissed Agents-tab row does not come back. See
Experimental typed launch units for the
directives that create it.
Agent Search¶
Press / or f on the Agents tab to open the auto-hiding filter bar. ,/ (leader
mode) starts forward inline deck search over the focused deck panel — the text of all
Main cards (with card titles as separators), the Files view content, or the Tools LLM
Calls text: type to jump to matches, Ctrl+R reverses the search direction, Enter
keeps the matches (Esc or Ctrl+C cancels), n / N move between them, y / Y
yank the current match or selection / its whole line, and Esc or q closes a
committed search. The filter bar uses the same structured Boolean Agent dialect as
Artifacts -> Agent and sase agent search, evaluated
against the live agents-live profile. Bare words match an agent's cl_name,
display_name, agent_name, and status, plus its macro, live reply/response, chat
transcript, and prior attempt replies through the text corpus. When
ace.current_project.seed_agents_query is on, sase's TUI seeds this query with the
current project's exact project: term on first load and marks it seeded until you
edit it. That setting defaults off because the same query also drives unread jumps
and prospective clans. While a filter is active, the Agents header shows it as
filter: <query> with any seeded marker, a [matched/loaded] count, and the edit-key
hint; clicking the query opens the filter bar.
The bar previews each valid edit against the loaded snapshot, Enter commits and adds
the previous query to history, Escape restores the pre-edit query and result, Tab
accepts completions, and ^ / _ walk query history while the bar is open. Each
successful Enter commit is remembered in machine-local SASE state and restored in the
next sase's TUI session; submitting an empty or whitespace-only query remembers the
unfiltered view. Typing, Escape, invalid submits, history preview, saved-slot
commands, and ,/ deck search do not replace the remembered query.
Restored Agents queries are applied before provider filtering and before current-project
seeding. A restored query does not push history, change saved-query slots, or write back
to disk until you submit again. Non-empty remembered records from the inactive query
dialect, or from an older agents-live profile, are ignored with a warning; explicit
empty records restore across dialects.
A leading # saves or deletes an Agents-tab saved-query slot in the agents-live
namespace: #3 status:FAILED, # status:queued, and #3 save, allocate, or delete a
slot without changing the committed query.
Property keys (closed allowlist):
| Key | Form | Matching behavior |
|---|---|---|
name, session, clan, project |
key:value |
Exact, case-insensitive identity fields; completions include observed values. |
kind |
kind:agent, kind:workflow, etc. |
Enum for agent/member/session/clan/workflow rows. |
status, provider, source, needs |
key:value |
Enums; source accepts axe / manual, and needs accepts input. |
role, workflow, model, cl, text |
key:value |
Case-insensitive substring fields; text: searches the full text corpus. |
machine, tribe, tab |
key:value |
Exact live operational fields; machine accepts here, aliases, and hostnames; tab is exact and main means no stored tab. |
pinned, unread, hidden, attention, retry |
key:true / key:false |
Boolean properties. |
since, until, after, before |
key:2h, key:today, key:YYYY-MM-DD |
Start and finish bounds; Nh / Nd / Nw / Nm, today, and ISO dates. |
min, max |
key:5m, key:1h |
Runtime duration bounds. |
attempt |
attempt:2 |
Retry attempt number. |
Boolean operators: juxtaposition is implicit AND; explicit AND, OR, and NOT (or
!) are honored, and parentheses group expressions. Bare and quoted strings are
case-insensitive; prefix a quoted string with c for case-sensitive matching, as in
c"FAILED".
Legacy token migration:
Legacy (agent_query) |
Unified (agents-live) |
|---|---|
type:workflow / type:run / type:running |
kind:workflow / kind:agent |
age>2h, age>=2h |
until:2h (started at or before 2h ago) |
age<5m, age:5m |
since:5m (started at or after 5m ago) |
status:foo (substring) |
status:FOO (enum, completion-assisted) |
project:foo (substring) |
project:foo (exact; completion-assisted) |
tribe: (bare, "any tribe") |
no direct equivalent; OR explicit tribes |
everything else (cl:, machine:, pinned:, needs:input, source:axe, text:, booleans, AND/OR/NOT, parens, quoting) |
unchanged spelling |
Parse failures are non-fatal: the filter bar renders the error inline and keeps the last
good result. When the bad token is a known legacy spelling such as age>2h or
type:run, the error appends the unified replacement hint.
A committed query also decides how much agent history is loaded. When the persistent
artifact index can answer every term exactly — cl:, model:, provider:, project:,
kind:agent / kind:workflow, and machine: (including a negated machine: filter),
combined with AND / OR — the loader selects matching agents from the whole archive.
Any other term (for example status:, name:, tribe:, tab:, or free text) is first
evaluated against the bounded recent-history window, and the header adds
filtered on partial history; loading full history... until a quiet-window full-history
reconcile finishes and older matches can appear. Whenever the applied roster is
knowingly less than the visible inbox for the current query — a fallback scan, a
truncated inbox, a query-incomplete read, or a committed-query change still loading —
the header shows a loading indicator instead (⟳ loading agents... with no filter
committed), which clears on the completing apply. Pushdown-miss queries are only partial
when the read was viewport-bounded or truncated; a non-truncated baseline read saw the
whole visible inbox.
Transcript files are read only by the background content-index worker while a query is
active and are cached by (path, mtime_ns) so auto-refresh stays cheap. Per-file reads
are capped at 512 KB; missing or unreadable files are skipped silently. Keystrokes do
string-level canonicalization only; row matching runs through the Rust-backed
agents-live query index.
Leader Mode (, prefix)¶
Leader mode is available on every tab. In the Agents tab it also exposes notification
shortcuts for the currently loaded agent list; global entries such as ,m and ,U
behave the same from other tabs. Unread-completed actions operate on terminal rows that
are loaded in the Agents tab; ,j can reveal a direct member hidden by a collapsed
clan. Help is not a leader command: press the app-level ? to open the Help modal.
| Key | Action |
|---|---|
,, |
Repeat the last leader command |
,/ |
Search the selected agent's metadata |
,h |
Run agent from home prompt context; bare prompts default to #git:home |
,H |
Collapse one fold by hint: the selected tribe's expanded folds from a row; every tribe's expanded folds and panel titles from a selected panel |
,j |
Jump to the next unread completed agent, revealing a collapsed clan when needed, and mark it read |
,J |
Jump to the next visible stopped/terminal agent, newest first, without changing unread state |
,u |
Mark all loaded unread completed agents read, across tabs and collapsed clans; with none newly unread, press again within 10 seconds to undo |
,m |
Open Launch Control (aliases, providers, tmux Agent; see Launch Control) |
,U |
Open Update panel (SASE, providers) |
,E |
Plan Everything from cached update snapshots and skip confirmation if runnable |
,y |
Open the Refresh panel with Full history highlighted (refresh Agents from full history when the panel is disabled) |
,R |
Show runners info |
,L |
Jump to the log entry for the most recent error toast |
,B |
Capture an Agents-tab reproduction bundle for debugging row disappearance or duplication |
,T |
Toggle continuous Agents-tab repro invariant checks and auto-capture on violation |
,r |
Revert focused or marked agent commits, including recorded linked repos |
,x |
Kill focused or marked agent(s) and edit their prompt(s) |
,X |
Kill & edit this session's last launched agent, ignoring marks and focus |
,<space> |
Run agent from current agent's PR (skips selection) |
,. |
Open the Prompts overlay on History |
,Ctrl+G |
Edit the newest prompt-history entry in $EDITOR (no overlay) |
,> |
Open the Prompts overlay on History with cancelled prompts visible |
,@ |
Open the Prompts overlay on Stash without auto-restoring a lone entry |
Here, "stopped" means a dismissable terminal row such as DONE, FAILED, PLAN DONE,
TALE DONE, PLAN REJECTED, PLAN COMMITTED, or EPIC CREATED; it is separate from
the Agents header's "stopped" attention bucket for rows paused on user action.
The CLI equivalent of ,x without the edit pause is sase agent restart NAME: it stops
the named agent and immediately relaunches the stored prompt under the same name.
If any agents are marked, ,x acts on that marked set instead of the focused row. Stale
marks are ignored; if any remaining marked agent has no recoverable prompt, sase's TUI
warns and leaves the set untouched. After confirmation, sase's TUI kills or dismisses
the marked agents and opens a prompt stack with one editable pane per original prompt in
mark order. Embedded --- inside an individual agent prompt stays inside that agent's
pane.
Each recovered prompt is marked for forced name reuse so the relaunch keeps the original
agent's name instead of claiming <name>1. The marker is a ! on the %id directive,
and its exact shape follows the prompt:
| Original prompt | Rewritten as |
|---|---|
%id:foo |
%id:!foo |
%id(foo) |
%id(!foo) |
| a clan member's prompt | %id(!<suffix>, clan=<clan>) |
| a session member's prompt | %id(!<suffix>, session=<session>) |
no %id directive |
unchanged — a fresh name is used |
Note that the clan and session forms carry the member's trailing <suffix> — the part
after the <clan>. prefix, or the session role segment — not the full agent name; the
membership keyword supplies the rest. An existing bead= value is carried across, and a
standalone clan: declaration is dropped in favor of the clan= keyword. The last row
is not a failure: a prompt that never named its agent has nothing to reuse, so it simply
relaunches under a newly allocated name. A serial session member is the one case where a
prompt with no %id is still rewritten, because its session= attachment comes from
the row rather than the prompt; session roots are not treated that way. Forced reuse of
a session member replaces only that member and its own descendants; the session root and
the sibling members are left untouched, so the session= attachment still finds its
parent.
sase's TUI is the surface that confirms that reuse, and it carries the authorization
through to the launch, so no second confirmation is asked for. Forced reuse cannot be
combined with alt/fan-out directives in one segment; such a prompt is rejected with an
explanatory error and preserved in prompt history, so you can reopen it from ,. and
split the launch.
,X targets the most recently launched agent this sase's TUI session accepted, instead
of the focused or marked row(s) — marks are ignored entirely, even when agents are
marked. Repeating ,X walks back through this session's launch history one accepted
launch at a time; a launch already killed or dismissed by hand is skipped. A launch
leaves that history only once its kill or dismissal actually starts, so canceling the
confirmation, losing the row mid-action, or failing to resolve the prompt leaves the
same launch as the next ,X target instead of walking back to an older one — and
pressing ,X again while that confirmation is still up neither opens a second one nor
advances. Once its target row exists, ,X reuses the exact ,x machinery above (the
same confirmation rule and forced-name-reuse rewrite), so a single-agent launch opens
one editable pane and a bulk-Patch or multi-prompt --- launch opens one pane per
launched agent in launch order. Nothing is recorded across sase's TUI restarts: the
targetable history is this session's in-memory launch stack, not disk state.
,X is meant to undo a premature <enter> press, including the moment right after
submit before the launched agent's row exists yet. In that in-flight window sase's TUI
restores the submitted prompt immediately (no confirmation — the ,X press is the
confirmation) and kills the launched agent(s) once the launch proc finishes. A
replacement submit from the restored prompt waits until that kill settles so the
original name is not resurrected underneath it. Repeat ,X while the kill is still
pending re-focuses the restored prompt and does not walk back to an earlier launch.
Restarting sase's TUI mid-flight drops the pending kill: the launch completes, the row
appears, and ordinary ,x applies.
Submitting a prompt returns immediately: <enter> removes the prompt bar on the next
repaint, even when the launch still has to wait, and the launch carries on as a pending
launch. The launch shows as a launch <name> row in the proc indicator and the Procs
tab from the moment you press <enter>; its message names what it is waiting for (for
example "waiting for kill/dismiss cleanup" while a ,x or ,X cleanup settles), and
the durable sase run row takes over when it is submitted. A launch that has not been
submitted yet has nothing to kill, so ,X on it simply cancels the launch and reopens
its prompt in a bar (still marked for forced name reuse, so a re-submit stays behind the
same cleanup). A cancelled bulk-Patch launch restores its shared prompt; the Patch marks
are not restored. Quitting sase's TUI while a launch is still pending stashes its prompt
so @ can restore it.
Press ,r on a DONE or FAILED agent to preview commits attributed to that agent
before creating git revert commits. For plan/follow-up sessions, sase's TUI reverts the
session scope when the row carries session metadata; otherwise it reverts the focused
agent name. The preview includes the primary workspace plus recorded linked_repos
metadata entries that still point at an existing workspace directory; never-opened
linked workspaces are not part of this action. Each repository is checked before
execution, and a dirty or non-git linked repo is reported and skipped while clean
repositories can still be reverted. Successful execution creates one revert commit per
repository, pushes when a remote tracking branch is available, and writes
revert_result.json beside the agent artifacts.
When agents are marked, ,r previews the combined commit set for the marked DONE /
FAILED rows. Marked agents must come from the same primary workspace. The bulk path
still groups work by repository, deduplicates overlapping session matches, skips marked
rows with no matching commits, and reports partial linked-repo failures instead of
hiding them.
Agents Tab Reproduction Bundles¶
Agents-tab reproduction bundles capture the loader/apply sequence that determines which rows are visible. Use them when the Agents tab briefly drops historical rows, re-adds them, or shows duplicate workflow parents.
When you see one of these bugs in a live sase's TUI session, switch to the Agents tab
and press ,B before refreshing again. sase's TUI writes a commit-safe bundle to
~/.sase/repros/<timestamp>-manual-.../agents_tab_repro.json and shows a toast with the
path. "Commit-safe" means local names and paths are redacted, and prompt, response,
chat, and diff bodies are omitted. The bundle keeps the row identities, loader state,
app projection state, screen text, and an SVG screenshot needed to replay the row-list
behavior.
Replay a bundle from a checkout of this repository:
sase repro replay tests/ace/tui/repro/fixtures/agents_tab_disappear_reappear_v1.json --assert-stable --json
The current expected verdict for the checked-in fixture is:
{
"result": "passed",
"failed_invariants": [],
"verdict": "current code fixed for the captured Agents-tab bug class"
}
Add --write-artifacts /tmp/sase-agents-tab-repro-artifacts to write one .txt screen
dump and one .svg screenshot per replay step. The replay JSON lists those paths in
screen_paths and screenshot_paths.
Use out-of-band capture only when you need a filesystem baseline and did not have the live TUI capture running:
sase repro capture agents-tab --output /tmp/sase-agents-tab-capture --commit-safe --json
Out-of-band capture is labeled capture_mode=out_of_band because it loads the current
filesystem state and cannot reconstruct transient refreshes that already passed through
the running TUI. The replay harness is scoped to the known Agents-tab
disappearance/reappearance and duplicate-parent bug class; it is not a general proof for
arbitrary rendering races.
For continuous diagnosis, press ,T on the Agents tab to enable invariant checks after
each load/apply cycle. On the first violation in a burst, sase's TUI auto-captures one
bundle under ~/.sase/repros/<timestamp>-auto-.../ and shows a warning toast. It does
not write a new bundle every refresh while the same violation remains active.
Bang Mode (! prefix)¶
| Key | Action |
|---|---|
!! |
Run background command |
!x |
Start / stop service host (or select process) |
!R |
Revive a previously dismissed agent |
Copy Mode (% prefix)¶
Press % to open the Copy as… palette for the focused agent. It supports mouse
selection, arrows or j/k plus Enter, and every configured direct accelerator
below. q/Esc cancels unless that key is itself configured for a copy target, in
which case the target wins.
| Key | Action |
|---|---|
%c |
Copy chat file path |
%E |
Copy file path |
%@ |
Copy the focused concrete agent's durable global @agent: reference |
%n |
Copy the focused agent's agent_name (falls back to display_name; toast indicates which) |
%p |
Copy agent prompt |
%r |
Copy the selected node's tool run id (when it has tool runs) |
%s |
Copy sase tui snapshot |
Keybindings: Services Tab¶
The visible tab is Services. Its sidebar is four stacked panels. The top Service
Procs panel holds every service proc assembled from built-in, plugin, user, and
machine-overlay configuration — daemon rows plus background commands (oneshot service
procs) in a separate ── oneshots ── section below the daemon procs. Below it, one
panel per routine declaring source holds that source's routines with their job rows
nested under each parent: User Routines, then Plugin Routines, then Builtin
Routines. Source means the first config layer that declared the routine, not the layer
that last changed one of its fields — a builtin routine with a user interval override
stays Builtin, and jobs always render in their parent routine's panel. Each panel border
title carries at-a-glance metadata (counts, status chips, and badges such as hidden
oneshots or a non-running scheduler). J jumps to the first row of the next panel and
K to the last row of the previous panel, wrapping around and skipping empty panels;
Ctrl+O returns to where you jumped from. j / k cross panel boundaries on the
underlying global list without selecting chrome. Service proc rows show their lifecycle,
enablement provenance, restart state, and bounded output. The dashboard status line
reports the service host state (Host: <state>). Project-local sase.yml service
entries are intentionally ignored.
Builtin routine rows start folded on first sight so the dense builtin list stays calm; a
folded row still carries its jobs' health as a red !N badge (failed, timed-out, or
missing-script jobs, counted from the same cached snapshots as the panel title). Folding
applies only once those snapshots have arrived, so a header-only pre-load never hides
rows behind a misleading fold. h / l fold and expand the focused routine, and an
explicit fold persists for the session. ace.services.fold_builtin_routines (default
true) controls the first-sight behavior permanently.
Once service status has loaded, the info panel leads with a host clause:
Services · host ● running 4d · sase.service while the host runs (uptime in its largest
unit when known, then the installed native service unit or detached),
Services · host ○ starting while the host is still starting (not a failure),
Services · host ○ stale · press !x to start when the heartbeat is stale, or
Services · host ○ stopped · press !x to start when the host is down. A host config
error renders inline (! <error>), and an unreadable snapshot renders its short reason
in the header. Disabled rows carry an inline provenance chip (disabled here for a
local override, disabled by <layer> otherwise), unavailable rows show
unavailable: <reason>, and other non-running rows carry the core's human summary (for
example crash-looping (3)) plus the restart count (· 5r) whatever the state.
On a selected top-level service proc, x starts or stops it, r restarts it, and !e
enables or disables it on this machine. Those actions are no-ops when no service proc is
selected; in particular, x does not toggle the host from a routine or job row in any
of the routine panels. Use !x to start or stop the service host itself. The SVC
footer pill shows the running/desired service-proc count (N/M), or a red ! when the
host or a service proc is unhealthy (see Service Health Pill).
The right-hand panel shows the selected proc's effective command, current and desired
state, restart policy, dependencies, last exit, restart decision reason, stop
provenance, any pending start/restart request, and output tail, plus the host config
error when one is set. The description sits in the sticky
description panel above it.
The canonical tab id is services (axe is still accepted as a legacy alias, for
example sase tui -t axe). Configuration keys such as ace.axe_description_expanded
and many internal row names keep the historical axe spelling, which is why the
detailed scheduler reference below still uses “Axe.”
Sidebar Row Taxonomy¶
The Services sidebar renders four row types across its four panels so the operational tree reads at a glance:
- Service proc rows live in the Service Procs panel with a solid left accent bar
(
▌) in the service teal, a[*]/[!]/[~]/[?]/[-]/[·]status marker, the proc name, and an optional state chip (restart count, disablement provenance, or the core's human summary). - Routine rows live in their declaring-source panel (User, Plugin, or
Builtin Routines) as top-level sections with a solid left accent bar (
▌) in the routine hue, a[*]/[!]/[·]running/error/idle marker, the routine name, an optional overrun roll-up (⚠N), a red health badge (!N) counting failed, timed-out, or missing-script jobs even when the row is folded, and an optional compactNc / Necycles/errors chip at the end. - Job rows are child rows indented under their parent with a
└─tree connector, a per-run status icon (✓success,!failure/timeout,?missing script,●running,*agent-launched,·no runs), and the job name in a dim-gold child hue. Disabled jobs remain visible with a quietdisabledchip but cannot be run manually. - Oneshot rows (background commands run via
!!) live in the Service Procs panel below the daemon procs under a dim── oneshots ──divider when both groups are present. Each row leads with a state glyph (▷running,✓exit 0,✗failed or killed), a muted#Nindex, and the command, and ends with a chip carrying the recorded exit code and age (for exampleexit 0 · 4m ago), so they cannot be mistaken for scheduled AXE work. Commands started before oneshots existed remain readable from their old slot directories (no exit code) while thebgcmd_legacy_slotssunset flag is on.
Description Panel¶
The right-hand dashboard keeps the selected service proc, routine, or job description in
a dedicated panel between the status line and scrolling output. Every row of the panel
carries a solid left accent gutter (▌) in the row's own hue — service teal for
service procs, routine gold and job copper otherwise — so the block reads as a
blockquote and stays visually distinct from the output pane below. Generated for_each
job instances also show their target key on the summary row. The panel stays fixed while
output scrolls and disappears for background-command and empty Services views. It shares
one session state across the Services tab: d on a service proc row also collapses
routine and job panels, and the reverse is true too. A service proc with no configured
description shows the dim-italic No description configured fallback, while a proc
missing from the snapshot hides the panel entirely. Oneshot rows get no panel.
The panel has two states, and d toggles between them for the rest of the session. The
summary row ends with a ▸ d / ▾ d disclosure hint whenever there is a body to reveal
and the row has room for it:
Collapsed — one row, ellipsized at the pane width:
▌ Complete finished hooks and start stale ones, with zombie detection ▸ d
Expanded — the summary, a blank gutter row, and the reflowed body:
▌ Complete finished hooks and start stale ones, with zombie detection ▾ d
▌
▌ Scans every Patch matching the axe query, completes hooks whose runner exited, and
▌ starts the next stale hook when a runner slot is free.
▌
▌ • Honors max_hook_runners; a full slot table defers work to the next tick rather than
▌ queueing.
▌ • Hooks still running past zombie_timeout_seconds are marked ZOMBIE and stop holding a slot.
The body is reflowed rather than replayed: blank lines separate blocks, a block whose
first line starts with -, *, or • renders as a hanging-indent bullet list, and
every other block is joined into one paragraph and re-wrapped to the current pane width.
See Description Grammar for the authored form.
ace.axe_description_expanded (default true) sets the state each sase tui session
starts in. d flips an in-memory session state and repaints from cached snapshot data —
it never reloads config, reads disk, or writes the toggle back.
An expanded panel never crowds out the job output it exists to explain. The dashboard
budgets max(3, min(16, floor(pane_height * 0.45))) rows for the panel, falling back to
10 rows before its height is known. If the rendered block exceeds that budget, the last
row becomes a dim … +N more · e marker on routine and job rows — service proc rows
read … +N more with no · e, because e does nothing there: nothing is silently
dropped, and e opens the AXE entry editor, whose first field is the full description
in a multi-line text area.
Because d belongs to the Services tab, show_diff is scoped to the Patches sub-tab.
Pressing d outside Patches no longer opens a diff for an unrelated Patch.
Dynamic Sidebar Width and No-Wrap Rows¶
Every sidebar row is rendered as single-line Rich Text with no_wrap=True and
overflow="ellipsis". After each refresh each panel computes its widest formatted row
and its border title, and the widest of the visible panels decides the sidebar width:
the AXE container resizes between a 35-cell minimum and an 80-cell maximum, clamped
further so the right-hand dashboard always keeps at least 40 cells. On terminals too
narrow to fit a label even at the clamped width, the row ellipsizes rather than wrapping
onto a second line, and a long panel title truncates with Textual's border-title
ellipsis.
Controlled-Output Highlighting and ANSI Fallback¶
Output in the dashboard right panel uses a semantic highlighter for sources whose shape is controlled by sase, and falls back to ANSI rendering for everything else:
- Routine aggregate logs (
[YYYY-MM-DD HH:MM:SS] [routine] message) get timestamp, routine name, status words (success,failure,timeout,running,error, …), PIDs, durations, exit codes, and counts colored by severity and consistent with the sidebar taxonomy. - Controlled job output — runner lifecycle lines such as
Launched proposal 1 as <name> (PID <pid>)use the same status-word, PID, duration, and count highlighting as other routine messages. - External job scripts and background command output are arbitrary text and stay
on the ANSI fallback (
Text.from_ansi) with the existing capping and tail-biased caching behavior.
Render cache slots are keyed on (source_id, source_type) so the semantic and ANSI
paths cannot collide for the same numerical identity.
Job Result Documents¶
Selecting a recorded job run composes three sections inside the existing scroll region:
- RESULT is always present and is derived entirely from the cached run entry. It includes the status, structured summary and reason, counters, proposal and launch rosters, evidence, dry-run/source markers, and any error or traceback.
- A job-authored structured report follows when the result document supplies one. Semantic tones map to the AXE palette, tables elide cells at wide widths and stack at widths below 60 cells, and all job strings are rendered literally rather than parsed as Rich markup or ANSI.
- OUTPUT contains the existing ANSI-rendered log tail and retains the waiting, failure, reason, and no-output fallbacks for runs with an empty log.
The card and report are cached by run identity, lifecycle state, completion timestamp,
and rendered width. They paint only from the in-memory job snapshot; navigation does not
read, stat, or glob the run files. Auto-scroll continues to follow active running and
launched output, but terminal runs open at the RESULT card so the report is not
scrolled off screen on selection.
Navigation¶
| Key | Action |
|---|---|
j / k |
Cycle the next / previous nav item in the focused panel (service proc, oneshot, routine, or job) and wrap inside that panel |
J / K |
Jump into the first / last row of the next / previous panel |
Ctrl+N / Ctrl+P |
Page through the focused job's run history (older / newer) |
' |
Jump to a current-tab entry by adaptive hint |
Ctrl+O / Ctrl+Shift+O |
Walk the link trail first, then the current-tab jump stack |
$$ / $1-$9 / $0 |
Follow the first / numbered job link, or open the complete links panel |
` |
Jump to an entry across all tabs |
g |
Scroll to top |
G |
Scroll to bottom (pins auto-scroll) |
Ctrl+D / Ctrl+U |
Scroll output down / up by half a page |
Ctrl+F / Ctrl+B |
Scroll output down / up by a full page |
Commands¶
| Key | Action |
|---|---|
a |
Add a routine, or add a job under the selected routine |
d |
Expand / collapse the description panel for the selected service proc, routine, or job |
e |
Edit the selected routine or job configuration |
E |
Open the selected recorded job output in $EDITOR |
+ |
Run agent |
r |
Restart the selected service proc, run an enabled selected job manually, or re-run the focused completed background command (!!) row |
x |
Start / stop the selected service proc; on a oneshot row, kill it (after confirmation) or clear it once finished |
X |
Clear output |
. |
Show / hide oneshot rows (the Service Procs title counts hidden ones as +N hidden) |
The a flow discovers installed sase_job_* executables and also accepts a custom
executable. Both add and edit open a single-page property sheet showing every schema
field, including unset and inherited fields. The active row's detail dock shows its
schema help plus effective, target-layer, and inherited values. Edits remain sparse: an
inherited field is not copied into the selected writable scope unless you touch it.
Compound and advanced fields expand in place as raw YAML, with inherit/reset available
for removing a target-layer override.
Editing a generated job row edits its immutable base job and warns that every generated
instance is affected. Before writing, the panel shows an exact effective before/after
preview plus a source-file diff. When the scheduler is running, the preview makes
restart explicit: save and restart the scheduler to reconcile it immediately, or save
only and leave the current scheduler configuration active until the next restart. E
remains reserved for opening recorded job output.
AXE Property Sheet¶
The panel opens existing entries in browse mode, with no editor focused, so property navigation works immediately. A new entry opens in cell mode on its first required property.
| Browse key | Action |
|---|---|
j / k / ↑ / ↓ |
Move to the next / previous property |
g / G |
Move to the first / last property |
Enter / i |
Edit the active value in place; toggle a boolean |
Space |
Toggle a boolean or cycle an enum forward |
h / l / ← / → |
Cycle an enum backward / forward |
Ctrl+R |
Mark inherit/reset; press again to restore the original value |
1…9 / Ctrl+T |
Select a numbered writable scope / cycle scopes |
Ctrl+S |
Build the validation and source-diff preview |
Ctrl+L |
Reload after a stale-write conflict while preserving the draft |
q |
Close the panel directly and discard unsaved edits |
Esc |
Close the panel |
| Cell key | Action |
|---|---|
Enter |
Commit a single-line value; insert a newline in a multi-line value |
Esc |
Leave INSERT for NORMAL, then commit and return to browse mode |
q |
Type q in INSERT; close the panel directly from NORMAL mode |
Tab / Shift+Tab |
Commit and edit the next / previous property |
| Vim keys | Edit through the standard VimTextArea layer |
Ctrl+S / Ctrl+R / Ctrl+T |
Preview, inherit/reset, or cycle scope while the editor is focused |
| Preview key | Action |
|---|---|
↑ / ↓ / Ctrl+D / Ctrl+U |
Scroll by line or page |
g / G |
Scroll to the top / bottom |
Enter |
Save, restarting the scheduler when it is running |
Ctrl+O |
Save without restarting the scheduler |
q |
Close the panel directly |
Esc |
Return to the property sheet |
Leader Mode (, prefix)¶
Help is not a leader command: press the app-level ? on any tab to open the Help modal.
| Key | Action |
|---|---|
,, |
Repeat the last leader command |
,h |
Run agent from home prompt context; bare prompts default to #git:home |
,m |
Open Launch Control (aliases, providers, tmux Agent; see Launch Control) |
,U |
Open Update panel (SASE, providers) |
,E |
Plan Everything from cached update snapshots and skip confirmation if runnable |
,L |
Jump to the log entry for the most recent error toast |
,R |
Show runners info |
,. |
Open the Prompts overlay on History |
,Ctrl+G |
Edit the newest prompt-history entry in $EDITOR (no overlay) |
,> |
Open the Prompts overlay on History with cancelled prompts visible |
,@ |
Open the Prompts overlay on Stash without auto-restoring a lone entry |
Bang Mode (! prefix)¶
| Key | Action |
|---|---|
!! |
Run background command |
!x |
Start / stop service host (or select process) |
!e |
Enable / disable the selected service proc on this machine |
Copy Mode (% prefix)¶
Press % to open the Copy as… palette for the selected AXE row. Choose with the
mouse, arrows or j/k plus Enter, or use a configured direct accelerator. q/Esc
cancels, with configured target keys taking precedence.
| Key | Action |
|---|---|
%o |
Copy visible output |
%O |
Copy full output |
%s |
Copy sase tui snapshot |
Services Control¶
| Key | Action |
|---|---|
Q |
Open the quit / restart menu |
Query System¶
Editing Queries¶
/ is the app-level query key on every Artifacts pane and on the top-level Agents tab.
Every pane with a filter session keeps a persistent idle filter row; / (or the local
f) focuses it for editing. On Agents, / (or f) opens the auto-hiding filter bar,
and ,/ starts forward inline deck search. Help is the app-level ? on every tab.
| Context | Default query key |
|---|---|
| Patches | / (or local f) |
| Stitches | / (or local f) |
| Beads | / (or local f) |
| Provider documents | / (or local f) |
| Files | / (or local f) |
| Artifacts → Agent | / |
| Agents tab query | / or f |
The Services tab has no query editor. Its ? help modal and the command palette both
still offer "Edit search query" there, but the action currently does nothing on
Services; use the tab's own filtering and navigation keys instead.
To save a query, prefix with #:
#3 "myproject"-- save to slot 3# "myproject"-- save to next available slot#3(no query) -- delete slot 3
On Patches, Artifacts → Agent, and the top-level Agents tab, these commands run inside the inline filter and leave both the active query and editor session in place.
On first open, when ace.current_project.seed_filters is on and the Patches query
carries no project: / +name term of any polarity or depth, sase's TUI appends a
visible project:<name> token for the current project (the configured project name,
never the ProjectSpec key). An explicit term wins, including NOT project:… and a term
nested inside parentheses. The seeded token is session-only: it is not written to
last_query.txt. Press p and choose All projects to remove it. Submitting the
query from the filter bar keeps the token as yours and persists it.
Saved Queries¶
Saved-query slots are kept separately for each Artifacts pane. On the Artifacts tab,
press 0 followed by a slot digit (1-9, then 0 again for slot 0) to load that
slot from the active pane's saved queries -- e.g. 02 loads slot 2. The active pane is
not switched; a slot saved for a different query dialect is refused with an error
instead of being applied. Esc or any other non-digit key after 0 cancels without
changing the query. Bare digits still select the corresponding visible Artifacts
sub-tab; the saved-query slot keys live behind the 0 prefix so the two never collide.
Press * on an Artifacts pane that supports saved queries to open the saved-query
chooser instead. Press a populated slot (1–9, then 0), move with j/k or the
arrow keys and press Enter, or click a row. q/Esc closes the chooser without
changing the query. The chooser shows the saved query text and marks the active query;
an empty chooser also repeats the save syntax. The chooser is unavailable from the
Agents and Services tabs.
Query History¶
| Key | Action |
|---|---|
^ |
Navigate to previous query in history |
_ |
Navigate to next query in history |
Query history is available on every Artifacts sub-tab whose pane contract enables
query_history, including provider-backed document panes, and inside the top-level
Agents tab filter bar. Each pane has its own durable previous/next stack, and Help shows
the active pane's stack with the configured previous/next key labels.
See docs/query_language.md for the full query syntax reference,
including boolean expressions, status shorthands, property filters, and searchable
fields.
Config Flags pane¶
Open SASE Admin Center with #, then Config > 02 Flags. The pane is the
keyboard-first control surface for every code-owned SASE feature flag. It shows
effective state, kind, default, provenance, saved machine preference, description,
removal bead, and removal horizon; it does not edit portable configuration files.
With the default-on admin_center_flags sunset flag, Config's nested catalog is:
01 All · 02 Flags · 03 Holds · 04 Launch · 05 Memory · 06 Snippets · 07 Macros
Disable admin_center_flags to drop Flags, leaving the six-child catalog numbered 01
All through 06 Macros. sase flag enable and sase flag disable remain available
either way; they are the recovery and automation surface when the pane is off.
The pane uses the Admin Center list/detail layout: a header with registered/on/saved
counts, a flag rail, a scrollable detail card, a hidden inline filter, and a one-line
footer. Effective on/off and source are shown separately from saved on/off. When
environment or root CLI -f/-F still wins, a yellow “forced for this process” warning
explains that saving restarts sase's TUI and the service host but will not take effect
until that override is removed.
| Key | Action |
|---|---|
/ |
Open an inline filter over key, description, kind, effective state, and provenance |
Esc |
Close/clear the filter before closing Admin Center |
j / k |
Move the rail; arrows, Home/End, and mouse clicks work the same way |
q |
Close Admin Center |
Enter/Space |
Open a cancel-first confirmation (OFF -> ON or ON -> OFF) |
r |
Reload the catalog |
0 then 1–7 |
Jump to a numbered Config child while Flags is visible (1–6 when the rollout flag is off) |
Confirmation is cancel-first. It names the flag, the current-to-target state, the saved state path, any shadowing source, and that sase's TUI and service host restart after TUI tasks and installation changes finish. Confirming writes only the machine-state file, waits up to 60 seconds for TUI-local work and installation mutations, then performs one controlled sase's TUI+service-host restart. Independent commands keep running. Durable operations started from this TUI still finish their result handling first, within the same 60-second wait. A restart failure does not roll back the saved preference.
Disabling admin_center_flags from its own row is supported: the confirmation says the
Flags pane will disappear after restart and gives sase flag enable admin_center_flags
as the recovery command.
See feature flags for precedence, the state file, and the CLI contract.
Global Keybindings¶
These work on all tabs:
| Key | Action |
|---|---|
Tab / Shift+Tab |
Switch between Agents, Artifacts, and Services tabs |
# |
Open SASE Admin Center home (repeat on home to resume the last section); inside a working section, jump to the alternate section (repeat to toggle back) |
. |
Artifacts: collapse/expand the relations panel; Services: show/hide oneshot rows; Agents: expand/collapse the jump panel |
: |
Open the Command Line: run sase commands without leaving the TUI |
; |
Open the context-aware Command Palette |
i |
Show notifications inbox |
+ |
Run a custom agent (opens project/Patch selection) |
Space |
Prefill the prompt with the most recently launched VCS macro (blank home prompt if none; Space then Ctrl+U for a blank prompt) |
Ctrl+G |
Open the agent editor pre-filled with the most recent VCS macro prefix |
Ctrl+L |
Dismiss all currently-visible toast notifications |
@ |
Restore a stashed prompt: a lone entry restores directly; several open the Prompts overlay on Stash (@@ pops the newest draft when several are stashed) |
$$ / $1-$9 / $0 |
Follow the first / numbered contextual artifact link, or open the links panel |
Q |
Open the quit / restart menu |
R |
Open the Refresh panel on Artifacts and Services (this tab, full history, usage, or everything). On Agents, retry the selected local or remote agent |
r |
On Agents, refresh (or open the Refresh panel). Artifacts and Services keep r for Patch workflow / Services run or re-run |
q |
Quit (first closes an open artifact viewer pane) |
? |
Show help modal |
The generic Open SASE Admin Center action and the first # always open a
lightweight landing page without mounting a working pane. Press # again while home is
visible to resume the last section that was successfully active. With no prior visit,
the repeated key stays on home and loads nothing. Inside a working section, the same key
takes on a second meaning: it jumps to the section you were in immediately before the
current one, and pressing it again toggles back — exactly two sections remembered, like
a two-slot alternate. A color-coded, clickable footer along the bottom of the working
section names the jump target (or explains that none exists yet). The numbered strip
remains clickable: 1 Config, 2 Logs, 3 Machines, 4 Procs, 5 Projects, 6
Statistics, 7 Tools, and 8 Updates. Tab enters Config, and Shift+Tab enters
Updates. Each working pane and its data are loaded only on first entry, then cached
while the modal remains open. Command-palette actions such as Open logs panel,
Open procs panel, and Open statistics, plus update shortcuts and indicators,
enter their requested pane directly and make a successful entry the next resume target.
Closing from home does not clear an older target.
Both the top-level resume target and the alternate are persisted machine-locally and
survive across sase's TUI process restarts. Within one running sase's TUI process,
closing and reopening Admin Center also remembers each selectable pane's last logical
entry by stable identity, plus the minimal scope or sub-tab needed to show it again.
Filters, marks, scroll position, loaded data, pane instances, Statistics controls, and
other pane-local state still end with the modal. If ace.keymaps.app.open_config_center
is rebound, repeat that configured key instead; the footer and landing page display the
effective binding and destination.
Inside every working section, ' is an Admin Center-wide entry-jump key: it paints
adaptive hints over that section's selectable rows using the same hint alphabet
described under
Navigation in Agent, Stitches, Beads, Provider Documents, and Files,
a hint character moves the selection there, ' again returns to the previous position
(or the first hint with an empty back stack), and Esc cancels. Each working section's
own keybindings table names its jump targets; two are deliberate exceptions. The
Statistics tab has no row cursor, so ' there arms the same numbered-view selection the
0 prefix already arms, using the visible strip numbers as hints. Config's nested
catalog is ordered 01 All · 02 Flags · 03 Holds · 04 Launch · 05 Memory · 06 Snippets
· 07 Macros when admin_center_flags is on, or 01 All through 06 Macros
(without Flags) when it is off; 0 then the matching digits selects those children,
while bare digits continue to belong to the active child or the Admin Center's top-level
tabs. The Updates tab's single merged list jumps normally across every section — SASE,
Plugins, and Agent CLIs alike.
Quit / Restart Menu¶
Pressing Q opens the quit / restart menu. When this TUI instance has in-process
work, the menu warns inline (N TUI tasks will be interrupted) and it offers three
actions:
1/s— quit sase's TUI and stop theschedulerservice proc (best effort: the TUI still quits if the stop fails); the service host and its other service procs keep running2/r— restart the TUI, leaving the service host running3/a— restart the TUI and restart the service host
Press esc (or q) to cancel and return to the TUI.
Durable procs, ! background commands, monitor turns, and service procs survive
quitting and restarting; the menu says nothing about them because they keep running.
A plain q quits sase's TUI directly when nothing would be interrupted. Otherwise q
shows a y/n confirmation (default No) listing the in-process TUI work that would be
lost; confirming quits, declining (or esc/q) stays in the TUI.
An unsent agent prompt does not, by itself, ask you to confirm. After any required
confirmation — and immediately when none is required — plain q and menu quit (1 /
s) write that prompt to Stash before exiting. The save is one Stash row: every
non-empty pane, in order, or the frontmatter alone when every pane is empty but
frontmatter is present. Plan Feedback and Coder Prompt bars are not saved. If the write
fails, the exit stops, the TUI stays open, and the error is
Failed to stash prompt draft — staying so no text is lost. Choosing 1 / s also
leaves the scheduler running when that save fails.
Restart (2 / r, or 3 / a) tries the same save. When it succeeds, the TUI says
Prompt draft stashed; press @ to restore after restart. When it fails, the restart
still exits and the draft is not written.
A submitted prompt that is still waiting to launch does not, by itself, ask you to
confirm. The scheduler batches in the next paragraph are separate: they are chop
launches that have not all been started. When a quit or restart actually exits, each
waiting prompt is recorded in prompt history as cancelled and copied to Stash so @ can
bring the Stash copy back. The exit still completes if that copy fails or does not
finish within 3 seconds. When a confirmation is shown for other in-process work, its
summary adds Your unsent prompt draft will be stashed when a draft is open, and a
count such as 1 pending launch will be stashed for @ when prompts are waiting.
Declining the confirmation writes nothing.
Options 1 and 3 also check for in-flight scheduler launch batches (a chop run that
has proposed launches but has not launched them all yet). When quitting would drop the
remaining launches — or when in-process TUI work would be lost — a y/n confirmation
(default No) lists exactly what would be lost. Option 2 confirms only for in-process
TUI work. Declining returns to the TUI without exiting or stopping anything; when
nothing would be lost, q and all three Q options behave exactly as before with no
extra prompt.
Command Palette¶
Press ; from any tab to open the Command Palette — a context-aware modal listing
every keymapped action that is currently runnable. The palette is the discovery surface
for the TUI: rather than memorizing every chord, you can search by command label, key
sequence (e.g. %n, ,A, zc), category, or alias.
Behavior:
- Only commands applicable to the current tab and selected entry are shown by default. For example, PR diff appears only when a PR is selected; service start/stop appears only on the Services tab; agent-specific actions appear only when an agent row (not a group banner) is focused.
- Each row shows the keybinding, the command label, and a category badge such as
Navigation,PR Actions,Agent Actions,Copy, orLeader. - A title-bar badge (
Agents,Artifacts, orServices) reflects the current tab. - Typing
:into an empty filter hops to the Command Line. - When the filter has no match, a fallback row offers
Runsasein Command Line, which opens the Command Line pre-filled with the filter text (without running it).
Keybindings inside the palette:
| Key | Action |
|---|---|
Type |
Filter commands (case-insensitive substring) |
: on empty filter |
Hop to the Command Line |
↑ / ↓ |
Move highlight |
Ctrl+P / Ctrl+N |
Move highlight |
Enter |
Run the highlighted command |
Esc |
Close without running anything |
The palette delegates execution to the same handlers that the keybindings use, so
behavior matches pressing the chord directly. Selecting a built-in mode subcommand (e.g.
%n to copy an agent name) runs the action without forcing you through the transient
prefix mode. Custom modes defined in sase.yml are also represented per-command.
The ; binding follows your configured keymap. To rebind it, set
ace.keymaps.app.open_command_palette in ~/.config/sase/sase.yml; comma-separated
keys in that setting are treated as alternate bindings for the same action.
Command Line¶
Press : from any tab to open the Command Line — a centered panel where you type
sase commands (the sase prefix is implicit) and run them without leaving the TUI.
Behavior:
- Selection-aware, fuzzy completion with a live signature line: the panel knows the
current selection, ranks the selected entity first, and shows advisory grammar
diagnostics.
Tabcompletes,Ctrl+Faccepts an active completion,Ctrl+Rsearches history, and;on an empty line hops back to the Command Palette. - The frame's borders carry the chrome:
❯ Command Lineand the working-context chip (⌂ +<project> · <path>) on the top border, the key hints for the current context and theN runningcount on the bottom one. Both borders recompose when the terminal resizes, so their ends stay aligned: the chip is middle-truncated first, and the hints give way to the running count. The frame is 96% of the terminal wide (at most 200 columns) and 80% of its height, centered;Ctrl+Ttoggles full height. - The completion popup (and, on terminals at least 140 columns wide, the doc peek) sits in a completion tray beneath the input and signature line. It never covers command output. The tray keeps a fixed height (up to 13 rows), so the input never moves as candidates change. The candidate text still lines up under the slot being completed.
- The transcript follows new output.
Ctrl+D/Ctrl+Uscroll it by half a page from the input in INSERT or NORMAL mode, taking over the input's delete-char / delete-to-line-start keys in this panel. Scrolling back to the bottom (or running a command) resumes following. - The completion popup shows an 8-row window that scrolls through every candidate; its
footer counts the highlighted row (
N of M) and shows the one key that fits the menu state. An empty line offersRECENThistory andFOR <selection>suggestions under section headings. A slot whose provider failed reads⚠ <kind> unavailable, and one with nothing to offer readsno <kind>; the note never carries over to another slot. Provider results are cached briefly (pending plans for 5 seconds, everything else 15) and forgotten when a command finishes; the next fetch then skips the providers' own disk cache, so a plan you just approved is not offered again. A result that arrives after you edited the line or moved the cursor is dropped. Project slots always merge the provider behind the projects the TUI already knows, and a path slot lists dotfiles once the name you type starts with.. - Every command runs as an ordinary durable proc (tagged
command-line, visible by default in Admin Center → Procs), so hiding the panel never interrupts anything. Finished command-line procs keep their own retention bucket of 50. - Run policies: most commands run as procs, some (editors, pagers, interactive tools)
run in the real terminal with the TUI suspended, and a few refuse with an alternative.
Declined confirmation commands render an explicit
Rrerun with-y;Rdoes nothing for other blocks. - Built-ins:
cd(pin a working directory),clear,help, andhistory. They run instantly with no proc.cdtakes a path,+<project>(the project's label as completion shows it, or+home), or-to unpin. - Block keys (
NORMALmode):oexpand,vpager,Kkill,r/Rrerun,eedit,y/Ycopy output/command,popen in Procs,xremove,iback to input.
Panel keys are configurable under ace.keymaps.command_line in
~/.config/sase/sase.yml: history (up/down, Ctrl+R), the ; palette hop,
scroll_transcript_down / scroll_transcript_up (Ctrl+D / Ctrl+U), and every Block
key above. Set an action to unbound to disable it; it also leaves the key hints. The
completion-menu keys follow the zsh menu-select contract and stay fixed: Tab /
Shift+Tab / Ctrl+N / Ctrl+P / ↑ / ↓ to move, Enter or Ctrl+F to accept,
Esc to leave the menu, → to accept ghost text at the end of the line.
The : binding follows your configured keymap. To rebind it, set
ace.keymaps.app.open_command_line in ~/.config/sase/sase.yml.
Machines Tab¶
Open the SASE Admin Center with #, then press 3 or select Machines. This is a
local inventory and administration surface for the controller plus every enrolled
remote. It does not contact all gateways when opened: remote rows begin with state not
checked and health unknown until you request an explicit status check. Remote
capacity remains not reported even after that check because the status response does
not carry runner capacity. The local row reports the controller's configured runner
capacity immediately.
The list shows Alias, State, Health, Capacity, Last observed, and Endpoint. The local
Alias is the controller's configured machine name even though its Agents query token is
here. The detail card adds capabilities, provider, pinned installation identity,
quarantine state, and the status message from the most recent check while this Admin
Center remains open.
| Key | Action |
|---|---|
j / k |
Move selection |
/ |
Filter aliases, endpoints, providers, installation identity, and quarantine |
s |
Run one bounded authenticated hello for the selected remote |
c |
Show the persistent connect/enrollment flow |
r |
Show repair guidance for the selected remote |
R |
Show the rename command for the selected remote |
x |
Show removal guidance for the selected remote |
y |
Copy the commands shown in the action card |
Enter |
Close Admin Center and open this machine's agent tab (or filter Agents when the tab strip is hidden) |
f |
Close Admin Center and open Agents filtered to this machine |
U |
Reload local machine inventory without probing gateways |
Connect, repair, rename, and remove are guidance actions: they display canonical CLI
commands and safety notes but do not mutate machine state themselves. Enter selects
the machine's agent tab when the tab strip is visible, and otherwise applies an Agents
query of machine:local for the local controller or machine:<alias> for a remote (f
always filters). See the Remote Dispatch Runbook for the
credentialed enrollment and recovery procedures.
Projects Tab¶
Open the SASE Admin Center with # and switch to the Projects tab with 5, Tab /
Shift+Tab, or the main tab strip. The tab contains a second clickable strip:
Projects · Repos · Workspaces. [ / ] cycle these sub-tabs while Tab /
Shift+Tab continue switching the main Admin Center tabs.
The Projects sub-tab lists true, non-system projects only, with enabled projects
first and disabled projects still visible. Here, "true project" means a project backed
by its own main ProjectSpec, rather than an internal linked-repo backing record; a true
project can be enabled or disabled. Rows show a CUR marker, the display/canonical
name, VCS kind (git or gh), lifecycle state, active claims, workspace/repo counts,
and warnings. Telemetry-only directories and linked-repo backing records cannot appear.
| Key | Action |
|---|---|
j / k |
Move selection |
' |
Jump to a row via adaptive hints, within the active sub-tab only |
/ |
Filter the current sub-tab |
[ / ] |
Cycle Projects, Repos, and Workspaces sub-tabs |
r / w |
Show repos or workspaces pre-filtered to the highlighted project |
Enter |
Run the highlighted project's default lifecycle action |
i |
Initialize the marked set, or the highlighted project if unmarked |
I |
Initialize every enabled project (sase init --all) |
m / u |
Toggle one mark / clear all marks |
e / A |
Edit the ProjectSpec / aliases |
a / d |
Enable / disable the highlighted project or marked set |
c |
Make the highlighted project current |
Ctrl+D |
Delete the highlighted SASE project directory or marked directories |
F |
Force the last blocked disable after confirming live-work checks |
R |
Reload records or the current inventory |
p |
Open the shared project picker on the Repos or Workspaces sub-tab |
Esc |
Clear an inventory project filter; otherwise close the Admin Center |
q |
Close the SASE Admin Center |
c requires the highlighted project to be enabled and launchable; pressing it on a
project that is already current, disabled, or not launchable reports why in the status
line instead of starting a write. All Projects-tab keys, including c, i, and I,
are configurable under ace.keymaps.projects.
Initialize from the Projects tab¶
i initializes the marked set, or the highlighted project when nothing is marked.
Disabled and system-managed rows in the mark set are dropped with a status message; if
nothing remains, sase's TUI warns and submits nothing. I always means the canonical
sase init --all inventory: marks, filter, and highlight are ignored.
Either key sets a status line immediately, then plans off-thread with
sase init … --check --json. When every target is already current, sase's TUI toasts
that and does not open a modal. Otherwise an initialization-plan preview shows the exact
apply argv, per-planner rows with the CLI's glyph vocabulary, and warnings and blockers
verbatim. The memory step may commit and push generated project memory; that warning is
shown with the same prominence as the CLI prompt. Confirm runs exactly one
sase init … --yes proc into the Procs tab and refreshes the pane in place. If the plan
has TTY-only blockers, t suspends sase's TUI into interactive sase init for the
blocked subset. Enter still means enable; initialization is never implicit.
The preview's own keys are listed on its border: y runs the plan, d toggles full
file diffs on and off, t hands the run to a real terminal when the plan has TTY-only
blockers (the row and key appear only then), and Esc, q, or n cancels. Ctrl+D /
Ctrl+U scroll a long preview. The run button is disabled outright when no project in
scope has a changed, runnable planner, and a plan whose runnable planners would
overwrite or delete a file is styled as a danger confirmation rather than a neutral one.
The current project — the same one the status-row project: +<name> chip names — is
marked on three surfaces at once, all in that project's accent color: a + in the row
table's CUR column plus its name rendered in that accent, a current:+<name> segment
appended to the summary line, and a +CURRENT badge on the detail panel's header line.
The detail panel also carries a dedicated Current project: line for the highlighted
row, whether or not it is current — stating the fact for a current row (and, when it
arrived via a Patch, which one) or the exact reason and fix for one that is not (enable
it first, it has no launchable ProjectSpec, or press c). This display always resolves
live and ignores ace.current_project.indicator, which only hides the status-row
cluster's project group; see Current project.
When one or more projects are marked, a, d, and Ctrl+D target the marked set
instead of only the highlighted row. Successful lifecycle changes clear the affected
marks; blocked or failed rows stay marked so you can inspect or retry them. Disabling
uses the same locked mutation path as sase project disable; live RUNNING claims or
artifact markers block it unless the F force retry is intentional.
The Repos sub-tab inventories every known primary, sidecar, linked, and opened
external repo for enabled projects by default. Rows show owning project, checkout
presence, and path; details include source, description, auto_clone, environment name,
and SDD storage mode. The Workspaces sub-tab joins every registry entry with its
claim, PID liveness, pin, last-used time, TTL staleness, and checkout presence. Missing
checkouts point to sase workspace repair, and dead claims are warning-styled. Both
sub-tabs load off-thread and show cached rows during refresh.
On first open, Repos and Workspaces seed their project filter from the
current project when ace.current_project.seed_filters is on. Press
p on either inventory to choose all projects, an enabled project (●), or a disabled
project (○). Explicitly selecting a disabled project is how its repos/workspaces
become visible. / then filters within that project scope; Esc clears the scope and
the cleared filter sticks for the rest of the session. The picker is filterable by
display name, canonical key, or state and shows repo/workspace counts for each project.
e suspends sase's TUI, opens the selected ProjectSpec in $EDITOR (falling back to
nvim), holds the ProjectSpec edit lock for the editor session, then reloads project
records. In this panel, Ctrl+D asks for confirmation before deleting the entire SASE
project directory: ProjectSpecs, project-local config, artifacts, and related state
under ~/.sase/projects/<project>/. Deletion is refused while the project still has
RUNNING claims or live artifact markers. It does not delete workspace checkouts, and
system-managed projects such as home are excluded from the panel.
Statistics Tab¶
Open the SASE Admin Center with #, then press 6 or switch to Statistics. Its
eight sub-tabs summarize overview, runners, projects, providers, agent activity, macro
usage, plan/question activity, and performance for the selected time range. The strip is
numbered 01 Overview · 02 Runners · 03 Projects · 04 Providers · 05 Activity · 06
Macros · 07 Plans & Questions · 08 Perf; press 0 and then the second digit to jump
straight to a view. The Admin Center-wide ' entry-jump key arms this same
numbered-view selection instead of painting row hints — Statistics has no row cursor, so
the already visible strip numbers act as its jump hints; Esc or any non-digit cancels.
Use [ / ] to move between views, t / T to cycle time ranges, c to enter a
custom range, p / P to cycle project scope, Ctrl+D / Ctrl+U to scroll, r to
refresh, and ? for the pane's key help. First open seeds the project filter from the
current project when ace.current_project.seed_filters is on; p /
P can always cycle away from that seed, including back to All projects. On
Overview, Agents Run, Success Rate, and Commits open Projects; Plans Proposed and
Questions open Plans & Questions.
The Perf sub-tab combines five headline measures—Startup (median visible-ready),
Stalls (stall count, with hitches named separately in the tile's detail line), Launch
(p95 total launch time), Agent p95, and LLM p95—with startup stages, stall/hitch events,
grouped latency and reliability, and source-coverage diagnostics. Press g to group
latency by subsystem, provider, or workflow; the grouping also decides what the count
column counts (LLM invocations, agent runs, or an ungrouped count) and whether a Share
column applies. Perf counts come from telemetry and TUI logs, not the artifact index, so
they are not comparable with the run counts on the other sub-tabs. Perf is global: the
project chip remains visible but is marked not applied. See
Reading the Admin Center Perf view
for data sources, retention, and probe details.
The Statistics Macros sub-tab reports macros referenced by agent launch prompts:
- By Usage ranks macros by runs and shows references, share, agents, success, runtime, and recency.
- By Model breaks each macro down by model.
- By Project breaks it down by project.
- Used With shows macros referenced together in the same run.
Press g to cycle those four groupings without reloading the underlying statistics.
Press x to choose one macro and replace the ranking with its full time, model,
project, provider, tribe, and co-usage breakdown; press X to clear that focus. The
range and project filters apply before all macro aggregation.
These counts come from each run's launch-boundary macros.json, recorded before prompt
expansion. A run counts once per macro name, while Refs counts distinct argument
variants of the same name separately. References introduced inside workflow step
templates are intentionally excluded. Historical runs appear after the agent-artifact
index rebuilds at the current schema.
Each record carries a kind — workflow, part, or swarm. A macro swarm is consumed
by the dispatcher before any agent starts, so it never appears as a lexical reference in
a child's prompt; instead the swarm is attributed to every agent it launched, and a
nested swarm records every link of its chain. Because swarm records carry no arguments,
Refs equals Runs for a swarm row. Attribution is forward-only: runs launched
before this feature shipped are not backfilled.
This Statistics sub-tab is distinct from Config's Macros child described in Macro Browser: that child browses and edits macro definitions, while the Statistics sub-tab measures how launch prompts used them.
Tools Tab¶
Open the SASE Admin Center with #, then press 7 or switch to Tools. Its three
sub-views cover Runs (silent runs pinned first in red, then live, then settled
newest first), Failures (failure-signature groups with run and agent witness
counts), and Catalog (the current project's named tools with LAST and TYPICAL from
the run ledger). The pane is filtered to the current project by default; press A to
widen it to all projects. Use [ / ] to move between views, / to filter Runs with
tool:, state:, agent:, and verdict: tokens, enter to focus detail (Failures
lists the group's affected runs newest first with their agents), a to jump to the
run's ⚒ Runs block on the owning Agents row, v to open the retained log in the pager,
y to copy the run id, s to stop the focused live run (same DANGER confirm as the
Agents tab), r to run the focused catalog tool at the current project root, and R to
reload. The Runs detail reuses the Agents tab Runs block renderer. The pane never
settles or reconciles runs; the only writes it offers are the explicit stop and
catalog-run flows. The top-bar tools: group opens this pane with all projects, so the
pane never shows fewer runs than the chip claims.
Launch Control¶
Press ,m from any tab to open Launch Control — one keyboard-driven surface for
launch configuration, model aliases, temporary provider routing, and launching an
interactive agent CLI in a new tmux window. The top level has three visible sections:
Launch settings, Built-in size aliases, and Your aliases. Consecutive
visible sections are separated by exactly one non-selectable blank row; there is no
leading, trailing, or doubled spacer.
Data rows use the grid ownership gutter | name | value/model | state. The former row
kind column is gone: labels such as launch, setting, role, user, and bucket do
not appear as their own column. User-owned aliases and buckets still have the tan ▌
ownership gutter, misplaced built-in aliases keep a gold ! marker in the name cell,
and collapsed buckets put ▸ directly before the bucket name.
Launch settings contains six rows: default model, epic lander,
big epic lander, big epic starts at, default effort, and max runners. The three
model rows show raw alias/config value → effective provider/model. big epic starts at
shows the effective bead.big_epic_phase_threshold as <N> phase or <N> phases:
epics with N or more authored phases use the big epic lander, while smaller epics use
the regular epic lander. default effort and max runners show their launch-effective
scalar values and any active temporary override state.
Alias rows show the alias name, effective provider/model as a provider-themed badge, and
a state tag — configured, implicit / implicit → @<fallback> /
implicit → @<fallback> @ <effort>, or an override · <time> left /
override · until cleared chip when a temporary override is active. Configured
references use the same configured → @<target> @ <effort> form. A model-specific
effort carried by an override appears beside the effective provider/model badge. When
one or more providers have temporary routing state, the title adds a compact line for
active priority and/or disables. Priority renders as priority: CODEX ★ <time>, hard
disables as disabled providers: CODEX <time>, and soft disables as
CLAUDE soft <time>.
The alias area is split into Built-in size aliases and Your aliases. Each header
reports the aliases represented by its rows (including members of collapsed custom
buckets) and its bucket count. Built-in size aliases always lists exactly five rows
in size order — @xsmall, @small, @medium, @large, @xlarge — with no bucket to
drill into; each row is edited, overridden, reset, or cleared directly like any other
alias row. Your aliases holds only user-owned aliases and custom buckets, in
alphabetical order. If there are no custom aliases or buckets, Your aliases remains
visible with a non-selectable hint naming llm_provider.model_aliases.custom.
Custom buckets group your own aliases under a shared display name, either through
model_aliases.buckets.<name> or a custom alias's bucket: tag; there is no built-in
bucket for a custom alias to join, so every bucket that appears is entirely user-owned.
Each collapsed bucket row reports the member count and active overrides, while the
description strip summarizes distinct effective models. Open a bucket with l, Right,
or Enter; return with h or Left. Inside the bucket, each alias keeps its own
configured/implicit state and can be edited, reset, overridden, or cleared
independently. A configured description under model_aliases.buckets.<name> replaces
the default. A custom bucket renders its bucket state in the ownership accent, and the
drilled-in title ends with · custom bucket plus the ownership glyph.
The two-line strip below the list explains the highlighted row. Launch settings show
their config path, effective value, and boundary/override context. Builtin aliases use
fixed descriptions. User aliases use
llm_provider.model_aliases.custom.<name>.description; a malformed user alias without
one shows that config path as the fix. A non-pool alias with an explicit effort uses the
second line to say whether it matches or overrides the configured default. For a
selector-valued alias, the strip lists every parsed member with an available/unavailable
marker. A round-robin pool's row state includes an availability count such as pool 2/2
that counts only pool members (a last-resort tail does not inflate the denominator), and
→ marks the exact next peeked selection without advancing its cursor — including when
the tail is selected. After the last pool member, a fallback: separator introduces
last-resort rows. An ordered fallback labels candidates in priority order, marks the
current winner, and never reads rotation state. The row's provider/model/effort badge is
derived from that same selected member. Temporarily hard-disabled providers count as
unavailable for this display and render as a red ×. Soft-disabled members stay
selectable and still count in pool <available>/<total>: a selected one renders as
amber → ✓ provider/model@effort, and a spared one as amber × provider/model@effort,
with no trailing soft chip. Temporary provider priority uses distinct wording: the
preferred member is labeled priority, and other usable members that remain behind it
are labeled backup. If a temporary alias override targets a hard-disabled
provider, the override is preserved but paused: the row shows the live fallback/pool
target, the state tag says the override is paused, and the description names the
disabled provider that must expire or be re-enabled before the override resumes. An
active override whose provider remains available, including a soft-disabled one, still
bypasses selector choice for the override's lifetime.
If a builtin size alias is mistakenly configured under
llm_provider.model_aliases.custom, opening the panel emits one warning toast listing
every affected @alias. A gold warning glyph remains on the affected alias row even
while a temporary override is active, and highlighting it replaces the normal
description with the same actionable advice. Move the entry's model value from
llm_provider.model_aliases.custom to llm_provider.model_aliases.builtin; sase's TUI
identifies the misplaced entry but does not rewrite the configuration automatically.
Because ownership follows the alias kind rather than where it is configured, the
misplaced alias stays in the Built-in size aliases section — never inside a custom
bucket — and does not receive the ownership gutter.
Navigate with j/k (or arrows / Ctrl+N / Ctrl+P) and act on the highlighted row.
Navigation, and jump hints, skip headers, spacer rows, and the empty-custom hint.
| Key | Action |
|---|---|
l / Right / Enter |
Open the highlighted bucket |
h / Left |
Back to the top level from an open bucket |
' |
Jump — paint adaptive hints; a hint moves the highlight without activating the row, and a second ' jumps back |
o |
Override — set/change a time-bound temporary alias/default-effort/runner-limit override |
x |
Clear — remove the active temporary override on the highlighted override-capable row |
e |
Edit — change the persistent configured value |
r |
Reset — unset an alias/model setting or the big-epic threshold |
p |
Providers — disable, prioritize, or re-enable registered providers for future routing |
u |
Usage — inspect cached provider subscription usage and request a bounded update |
t |
tmux Agent — launch an interactive agent CLI in a new tmux window |
H |
History — view recorded prior runs for the highlighted alias, alias-backed launch setting, or bucket |
Ctrl+E |
Effort — persistently edit, temporarily override, or clear the global default effort |
Ctrl+R |
Limit — persistently edit, temporarily override, or clear the global runner limit |
Esc / q |
Close the panel |
Providers · Usage¶
Press u in Launch Control to open the read-only Providers · Usage view. The same
view is available from the global command palette as Open Providers · Usage, with
search aliases including usage, quota, limits, capacity, subscription, and providers.
The first paint reads only the local usage cache. Each provider row shows remaining capacity or collection status, applicable account scope, freshness, and an Updating… marker when a durable refresh is live. Select a provider for its plan, account mode, collection status, last observation, and per-window remaining percentage, reset, age, state, and source. The layout drops the meter and combines detail columns as the terminal narrows.
Rows reserve the orange ⚠ failing badge for collectors with three or more consecutive
probe failures. The selected provider detail shows collector health for both degraded
and failing states, including the failure streak, failing-since age when known, and last
successful probe age when known. This health describes the collector pipeline only:
stale-but-numeric allowance data can still render, passive stream observations do not
hide a dead probe pipeline, and the indicator never changes launch routing.
The application header groups independently selected usage windows by provider on the right of the existing title row, for example:
🎭 62% 3d4h · fable 0% 1d8h 🤖 81% 5d2h
The title is centered on the header line. Usage occupies a right-docked reserve sized so the complete title still fits centered; changing usage text never moves the title, and the navigation and routing controls on the row below stay put. The complete title has priority: sase's TUI never truncates it merely to show more usage, and a clipped title keeps the full string in its tooltip.
The rendered segment owns one quiet space before the first provider icon and one after
the block. Each provider icon appears once, followed by one space, and providers are
separated by two quiet spaces with no inter-provider punctuation. Windows of the same
provider are joined with a middle dot (·). A single visible window has no extra
separator even when hidden windows remain.
The default weekly all-model window renders as <remaining-percent> <reset-countdown>;
additional selected windows include compact names such as fable, 5h, mo,
5h/fable, or 5h/gemini. Compact names omit redundant weekly and all-model components
and drop the family: prefix of family-scoped windows (5h/3p, not 5h/family:3p)
while retaining model/family distinctions; tooltips (scope: family: 3p) and
sase usage list (scope=family:3p) still show the session scope; scope? means the
provider did not expose exact applicability. The name, percentage, and reset countdown
share the window's ten-step remaining-capacity color, from red (nearly exhausted) to
blue (nearly full), except an exact 0% highlights the window's whole value run — its
name, percentage, and reset countdown together — with the inverted red style. Provider
icons, middle dots, provider gaps, and outer padding keep their normal surfaces; a
non-zero window's name and rejected marker do too. Structural punctuation is neutral,
normal weight in both themes. Which windows appear, and at what remaining percentage, is
fully configurable through
llm_provider.usage_metrics.indicator.
Stale or unknown-age numeric observations render with neutral text and disclose their
freshness in the tooltip instead of adding a visible marker. <1% remains a low but
nonzero reading on the normal badge surface. ?% 0h0m↻ means the window's reset has
passed and sase's TUI is awaiting a new observation, retaining the last known percentage
only in the tooltip. A bare ? in the countdown position means the provider never
reported a reset time. ! marks a vendor-rejected window that still shows remaining
capacity. An exact 0% window never shows it, because the inverted red run already
signals exhaustion, so every provider's exhausted window looks the same whether or not
its vendor reports a rejection. Collector failures no longer add a header warning glyph;
selected failing windows keep collector-failure prose in the tooltip, and model picker
hints keep their own separate ⚠ usage failing identity.
Provider groups always render in provider-name order. Within a provider, the default
weekly all-model window is first, followed by additional windows by window key.
Attention is shown only through color and markers; it never changes order. sase's TUI
shows the longest prefix of complete windows that fits the measured header remainder
after the icon and unclipped title, allowing the final provider group to be partially
visible, before adding a +N overflow count. If no complete window prefix plus
disclosure fits, sase's TUI falls back through usage N, N, and …, relaxing the
text-only padding at one-cell boundaries. Every selected and overflowed window's full
identity, exact key, precise percentage, scope, effective display policy, and reset
timestamp are in the tooltip. Clicking the usage cluster — including a window, +N,
usage N, a bare count, or … — opens Providers · Usage at the first displayed
provider and does not expand the header. Clicking the routing pill still opens Config >
Launch. The Usage command is also reachable from Launch Control's u and the command
palette when there is no display space at all.
Press u inside the view to submit or join bounded refresh work for eligible providers.
The modal stays responsive, reattaches to an in-flight refresh when reopened, reloads
the cache as operations settle, and reports partial failures by provider. Enter
focuses the detail table, Tab moves focus, j/k navigate, and Esc/q closes.
Collection requires llm_provider.usage_metrics.enabled; see
Subscription Usage.
On big epic starts at, Enter and e open a focused positive-integer editor, and r
previews a reset. The input accepts an unsigned base-10 whole number with minimum 1
and no spaces, signs, floats, or booleans; the inline constraint reads
minimum 1 · package default 5. The preview targets the writable user-base sase.yml
or its chezmoi source at bead.big_epic_phase_threshold. Reset uses an unset operation
so lower-precedence or package defaults resume; it does not write a literal 5. There
is no temporary threshold override. Pressing o or x on that row warns and points
back to Edit/Reset.
After a successful threshold write, Launch Control reloads the effective value and the
epic-lander row descriptions from the same provider/launch snapshot. If a
higher-precedence layer keeps the requested user value from winning, the notification
reports the actual effective value and the requested value. Dirty Git-backed targets
receive the standard tracked commit/pull/push offer with
chore: update big epic phase threshold.
Default effort controls¶
Ctrl+E works from every alias and bucket row because default effort is global. The
Default Effort card shows the exact value used for new launches and, while a
temporary override is active, the configured value beneath it. Press e to edit
permanently, o to override temporarily, or x to clear the active temporary override;
x is shown only when there is something to clear. Explicit prompt effort and effort
carried by an alias or selected pool member still win, and already-running agents are
unaffected.
Both Edit and Override open the same ordered, single-key effort ladder: 1 none, 2
minimal, 3 low, 4 medium, 5 high, 6 xhigh, and 7 max. Edit also
offers 0 Provider default. That option writes the schema's empty sentinel into the
user-base sase.yml, deliberately masking a lower-precedence package/plugin value;
Override does not offer a pseudo-level, because cancelling or clearing honestly resumes
configured/provider behavior. Config-derived values are best-effort: a provider that
cannot honor a level retains its provider behavior.
Temporary effort Override reuses the model-alias duration workflow unchanged: 15m,
30m, 1h, 2h, 4h, Until cleared, a combined custom duration, or t for an exact
local time/date. It is machine-wide state in ~/.sase/llm_effort_override.json; setting
replaces the prior effort override, expiry is enforced on the next launch, and Clear is
idempotent. This state is independent of ~/.sase/llm_override.json, which stores
concrete model-alias overrides.
Permanent Edit always targets the writable user base config rather than a
project-local layer. Its preview shows llm_provider.default_effort, configured
before/after values, the actual target, validation, and the source-preserving YAML diff.
With use_chezmoi: true, the actual write goes to the chezmoi source and the target is
applied before sase's TUI reports success. A dirty Git-backed target receives the usual
tracked commit/pull/push offer with chore: update default model effort. An active
temporary override remains launch-effective after this write until it expires or is
cleared; the preview and success notification both make that explicit.
Runner capacity controls¶
Ctrl+R is a fixed Launch Control binding and works from every alias, collapsed bucket,
and open bucket. It is not a leader-keymap setting. The Runner Capacity card shows
the current effective capacity budget and, while a temporary override is active, its
remaining time plus the configured value. Press e to edit the user-base configuration,
o to set a temporary machine-wide override, or x to clear an active override.
Edit and Override open a focused positive-integer card. Edit is prefilled with the
configured value; Override is prefilled with the current effective value. The input
accepts an unsigned base-10 whole number with minimum 1 and no product maximum. A
persistent edit previews the exact max_running_agents path, actual user or chezmoi
source target, configured before/after values, validation diagnostics, and
source-preserving YAML diff. Its tracked commit offer uses
chore: update max running agents. A higher-precedence overlay can keep the configured
effective value different from the requested user-layer value, which the reload
notification reports truthfully.
Temporary Limit overrides reuse the same relative/custom/exact-time duration cards as
model and effort overrides. The versioned machine-wide record is
~/.sase/max_running_agents_override.json; setting a value replaces the previous
runner-limit override, now >= expires_at expires it, and Clear is idempotent. A
persistent edit does not clear a live temporary override. Lowering the effective cap
never stops an already-running agent: occupied capacity may temporarily exceed the cap,
and new work waits until enough capacity drains. Raising the cap lets eligible parked
agents advance through the existing priority/FIFO gate on their next poll. Launches with
an explicit %queue(capacity=N) use that positive-integer budget for their own
admission decision, so they can intentionally start above the global budget while still
holding an ordinary weighted claim once admitted. Question continuations reacquire
against the current effective global cap after their gate turn has released capacity.
Provider routing controls¶
Press p from Launch Control to open Provider Routing. The modal lists every
user-facing registered LLM provider in stable order with its model count and one of
these states:
| State | Meaning |
|---|---|
available |
The provider is registered and its declared CLI is present. |
CLI missing |
Automatic alias routing already skips it because its CLI is missing. |
disabled · manual · <time> left |
Launch Control manually hard-disabled it until expiry or clearing. |
disabled · usage-limit automatic · <time> left |
Usage-limit detection automatically hard-disabled it. |
soft · manual · <time> left |
Launch Control manually soft-disabled it until expiry or clearing. |
soft · usage-limit automatic · <time> left |
A usage-limit automatic disable that was flipped to soft. |
★ priority · <time> left |
This provider is preferred in pools that include it. |
backup · CODEX priority |
This provider remains usable behind the active priority provider. |
CLI missing · ★ priority unavailable |
Priority intent remains, but routing uses backups until it can run. |
Hidden testing providers stay out of this human-facing modal. Disabling a provider does
not unregister it, change sase.yml, change model aliases, or stop provider processes
that are already running. A hard disable (d / Enter) keeps today's fail-closed
behavior: new launches, follow-ups, later retry/fallback resolution, model pickers, and
%model completion drop that provider. A soft disable (s) spares the provider in
| pools while another member can cover, never diverts a || fallback, and still
accepts explicit %model / picker / completion choices for that provider. Provider
priority (p inside the modal) is temporary preference state, not a disable: pools
prefer that provider when it is a usable member, while explicit choices and ||
fallback order continue to do what they say.
On any row, press d or Enter for a hard disable or s for a soft disable, then choose
how long. The duration picker is the same set of choices as alias overrides: 15m,
30m, 1h, 2h, 4h, Until cleared, a custom duration, or t for an exact local
time/date. When the highlighted row already has an active disable in the other mode,
the picker's first row is x Keep current window (<time> left) — one keypress to flip
hard ↔ soft without re-choosing a window. That row is omitted when the mode already
matches, so it is never a no-op. On a disabled row, x enables the provider
immediately, including an automatic usage-limit disable. Pressing x on an enabled row
warns without mutating state. Successful changes refresh the provider rows, Launch
Control title, alias routing rows, and the top-bar indicators without closing the modal,
so several providers can be managed in one pass. Unknown disable sources are shown as
readable labels instead of being folded into the manual state.
On an enabled, installed, user-facing row, press p to set or change provider priority,
then choose the same relative, until-cleared, custom, or exact-time duration used by
provider disables. Press c to clear the active priority, even when no selectable
provider rows remain. The modal keeps the current selection after writes and refreshes
the rows in place. If another session changed priority between the modal's snapshot and
the attempted write, the modal reloads the current state and asks you to press p or
c again against the fresh snapshot. If a write commits but the follow-up refresh
fails, the toast says the routing write succeeded and leaves the modal open for retry.
A sparing (soft) pool member still counts toward pool <available>/<total> and remains
selectable. In the alias description it renders as amber → ✓ provider/model@effort
when selected or amber × provider/model@effort when skipped, with no soft chip.
Priority members render with priority and backup members with backup, including in
the guided selector builder, model picker, and %model completion. Soft-disabled
providers stay in the model picker (header labelled soft, rows dimmed one step) and in
%model completion (annotated soft in the provenance column). Hard-disabled providers
are still omitted from both.
sase's TUI also shows active provider routing state as pills inside the violet
overrides: top-bar group (the launch-default and current-project chips moved one row
down, to each tab's status-row launch-context cluster). An active priority renders like
overrides: CODEX ★ 42m (with a state word only for soft-disabled or unavailable, such
as overrides: CODEX ★ soft-disabled 42m). One hard-disabled provider renders like
overrides: CLAUDE off 42m; one soft-disabled provider renders like
overrides: CLAUDE soft 42m. Several disables render the most severe (hard first)
provider plus a count, such as overrides: CLAUDE +2, and use the soft palette only
when every active disable is soft. When alias, priority, and disable facts are active
their pills sit side by side under the one overrides: label. Each pill keeps its color
and its tooltip section, and clicking the group opens Launch Control.
Disabled-provider launch panel¶
When sase's TUI is about to launch an agent that can only run on a hard-disabled provider, it opens a one-keypress panel instead of submitting a launch that would fail at invoke time. Soft disables never open this panel. After the instant input and project-tag checks pass, the prompt bar is released and the accepted launch appears as a preparing proc row while the provider check runs. Aborting restores the prompt when the bar and screen are free; if the user is already typing or another modal owns focus, it saves the prompt to the stash instead and never steals focus.
The panel is one blocked agent at a time. A four-agent swarm with two blocked units shows the panel twice in sequence. Enabling a provider while resolving one agent can unblock a later one, so sase's TUI re-checks after every write.
| Key | When it appears | Action |
|---|---|---|
e |
Always | Enable every provider blocking this agent, then re-check and continue the launch. |
s |
Always | Soft-enable those providers (same remaining window) so this agent can run while pools still spare them. |
1…9 |
Only when two or more providers block this agent | Enable that one provider and re-check. The panel stays up if another blocker remains. |
m |
Only when every slot of this agent agrees on one model | Open the model picker and rewrite this agent's %model. A fan-out unit shows a dim edit-the-prompt line instead. |
a |
Always | Abort this agent. The other agents in the launch still start. |
A |
Only when the launch has more than one agent | Abort the whole launch. The label states the real agent total. |
esc/q |
Always | Same as a. |
A single-agent launch that only enables or soft-enables submits the original prompt. Picking a different model on a one-agent launch rewrites that prompt. Dropping or re-modelling a unit in a multi-agent launch submits only the agents that remain.
Automatic provider drain¶
After a manual hard disable in Provider Routing (p from Launch Control, above)
newly hard-disables a provider, sase's TUI automatically relaunches the agents that
disable just stranded. No prompt is shown. The drain is submitted only when all of these
hold:
- The write was a hard disable (a soft disable never drains — it strands nothing).
- The write transitioned the provider into hard-disabled (no disable → hard, soft →
hard, or an expired disable → hard). Re-hard-disabling or extending an already
hard-disabled provider never re-drains; use
sase agent drain <provider>for that case. - The
provider_drainbeta flag is enabled.
The disable write returns immediately with the usual "disabled" toast, then a
Draining <PROVIDER> start toast follows. The drain runs as the durable agent.drain
proc (sase agent drain <provider> --yes --json) with the shared
provider-drain:<provider> concurrency key the automatic usage-limit path uses (see
Draining a Disabled Provider), so it appears in
the Procs tab and dedups against an automatic drain already in flight for that provider
rather than running twice. A completion toast summarizes the durable result envelope,
and the Procs row holds the full JSON envelope. If the TUI exits first, the proc still
finishes and its result stays in Procs / sase proc show.
An empty or no-longer-disabled automatic drain records a successful no-op: when the
drain finds nothing to relaunch (nothing_to_drain) or the provider is no longer
hard-disabled by the time the proc plans (not_disabled, soft_disabled), the proc
succeeds with exit 0 and the completion toast says nothing was drained.
Draining onto a chosen model is still available as
sase agent drain <provider> -m <model>.
tmux Agent¶
Press t from Launch Control to open tmux Agent. The panel lists every registered
provider that declares an interactive CLI, in shortcut-key order, and launches the
chosen one in a new tmux window in the current pane's directory. The same catalog and
launch engine back sase tmux-agent on the command line; only the presentation differs.
These windows are unmanaged agent CLIs, not SASE agents — they do not appear in
sase agent list.
If sase's TUI is not running inside tmux, t warns
sase's TUI is not running inside tmux; start sase's TUI in a tmux window to launch agent CLIs.
and does not open the panel.
Each row is <key> ● <display name> <vendor> <state>: the key in the selector
accent, the bullet and display name in the provider's accent color, the vendor dim, and
the state one of:
| State | Meaning |
|---|---|
ready |
The CLI is installed and launchable. |
not installed |
The row is dim and unselectable; the description strip shows the install hint. |
routing disabled · <time> left |
Hard-disabled for SASE automatic routing, but still launchable from here. |
soft · <time> left |
Soft-disabled for SASE routing; still launchable from here. |
A disabled-for-routing provider stays selectable because a human choosing an agent CLI is not automatic routing. The annotation is visible, not a wall.
The two-line description strip shows the exact command that will run, then the preview
window name (ai, ai2, ai3, …) plus effort and bypass state. A skipped
config-default effort is named rather than swallowed. Bypass is on by default (matching
the shell script this feature replaces) and is always visible in the strip; it is never
applied invisibly.
| Key | Action |
|---|---|
Enter / the row's assigned key |
Launch the highlighted (or keyed) agent CLI |
s |
Launch once without the provider's approval-bypass args |
j/k (arrows, Ctrl+N/Ctrl+P) |
Navigate; not-installed rows are skipped |
Esc |
Close and return to Launch Control |
q |
Close, but only when no provider claimed q (Qwen keeps q; the title and footer say so) |
A successful launch notifies Opened tmux window: <name> · <display name> and dismisses
back to Launch Control. A failure notifies with an error and leaves the panel open.
See sase tmux-agent and the tmux_agent
config block for the CLI surface, the drop-in tmux binding, and the documented parity
recipe against the script this feature replaces.
Alias History¶
Press H on an alias row, an alias-backed launch setting (default model,
epic lander, big epic lander when configured as a raw @alias reference), or a
collapsed bucket to open Alias History — bounded prior runs for that alias or, for a
bucket, every member alias. A concrete (non-alias) launch setting and the
default effort, max runners, and big epic starts at scalar settings are not
aliases; pressing H on one of those rows only shows a warning toast. The panel loads
off-thread and never changes Launch Control's own state.
The title names the alias or bucket, keeps the tan ownership accent for a user-owned source, shows the effective provider/model/effort badge when a single alias supplied it, and reports the total recorded, returned (currently visible), and done/failed/running counts. A bucket's runs are grouped under a disabled header per member alias, separated by the same single-spacer convention used elsewhere in Launch Control; headers, spacers, and per-group empty hints are never jump targets. Rows render newest first exactly as recorded, with a status marker, relative time, agent/workflow identity, the configured project display name, a provider-themed model badge with effort, and one of four provenance chips:
| Chip | Meaning |
|---|---|
direct |
An explicit %model directive named this alias. |
default |
The configured default model resolved to this alias. |
via @<...> |
This alias was reached indirectly, through an earlier alias in the chain. |
unrecorded |
No alias origin was captured for this run — recording predates it. |
The fixed detail strip below the list explains the highlighted run: the recorded alias
trail resolved to its concrete provider/model/effort, the same origin explanation as the
chip (an honest, non-speculative note for unrecorded rather than a guessed reason),
and the prompt snippet plus whichever of project, workspace, bead, Patch, start time and
duration, retry attempt, hidden state, and macro context are actually present — nothing
is invented for a field the query did not return. The returned window is a display
limit, not the full retention history; the title's recorded/shown counts and the
footer's "more available" hint make the difference visible.
Below the detail strip, a compact model-usage region ranks the models the currently
shown runs actually used. Counts cover the loaded window only, after deduplicating the
same artifact directory when a bucket lists one run under more than one member alias; a
dim (deduped) marker appears when that filter removed rows. Bar color follows the same
provider palette as the PROVIDER(model) badges above. Configured selector members with
no runs in the window render as unused; a model that ran but is not in the current
pool is tagged off-pool; runs with no recorded model collapse to one unrecorded row.
When every counted run for a model shares one effort the row shows @ <effort>; mixed
efforts show a dim @ mixed. At most four rows render; a +N more overflow row carries
the leftover count and percent so the shown shares still total 100%. Ctrl+J, Ctrl+K,
r, and . recompute the strip with the new window; j/k never do.
| Key | Action |
|---|---|
j/k (arrows, Ctrl+N/Ctrl+P) |
Navigate |
' |
Jump — adaptive hints over selectable runs only |
Enter |
Open the highlighted run's full prompt in the preview panel |
y |
Copy the highlighted run's durable @agent:... reference |
Ctrl+J |
Load more — add 10 runs to the per-alias limit and reload, keeping the highlighted run |
Ctrl+K |
Unload — subtract 10 runs, never dropping below the initial model_alias_history_limit window |
r |
Refresh — revalidate this load, bypassing the cache once |
. |
Toggle hidden runs in or out of the results |
Esc / q |
Close and return to Launch Control, unchanged |
A run without a durable agent name warns instead of copying a guessed reference, and a
missing or unreadable raw_macros.md warns instead of closing the panel.
Temporary overrides¶
Edit and Override open the shared model picker with an ALIASES group before the
provider-grouped concrete models. Alias rows show the exact @name token and its
current effective provider/model; filter by either @medium or medium, an alias kind
or description, or the displayed target. For persistent edits, the current alias and any
alias that would introduce a direct or transitive cycle remain visible but unavailable
with a concise reason. Custom... accepts a concrete model string, provider/model
path, or bare @alias reference in both flows and applies the same safety check to
free-form @alias values. Concrete model rows for hard-disabled providers are
omitted; soft-disabled providers stay in the picker (header labelled soft, rows
dimmed one step), priority providers are labeled priority, and priority backups are
labeled backup. Alias rows remain visible and show their current live fallback target.
Free-form explicit input is validated before submission and reports the same
disabled-provider diagnostic as a launch. In Edit, Custom... additionally accepts a
typed | pool or || fallback expression and opens prefilled with the alias's current
value, so changing one member of an existing selector no longer means retyping the whole
expression; Edit also offers a guided Pool / fallback... row next to Custom...
that builds a selector from the picker without typing | by hand (see
Persistent edits below). In Override, a typed pool or fallback is
refused outright with a message pointing at e — selectors are config-only and
overrides take a single target, so Override's Custom... never shows the
Pool / fallback... row.
Override continues from the picker to the duration picker (15m, 30m, 1h, 2h,
4h, Until cleared, or a custom duration like 45m, 1h30m, 90m). Press t in
the duration picker to choose Until a specific time. The focused time popup accepts
local forms such as 5pm, 5:30 PM, 17:30, 1730, today 5pm, tomorrow 9am, and
2026-07-12 09:00. An undated clock means its next occurrence (later today or
tomorrow); an explicit day/date must still be in the future.
The popup previews the resolved weekday/date, local time and abbreviation, configured
IANA timezone, and remaining duration before Enter writes anything. Daylight-saving gaps
are rejected; repeated fall-back times require an offset-qualified ISO value such as
2026-11-01T01:30-04:00. Invalid input stays focused with an inline explanation. Esc
goes back to the duration picker, where a second Esc cancels the override flow.
Overrides are per-alias and per-launch-setting, and independent:
- An override on
default modeldrives the no-%modellaunch default. It renders in a gold status-row pill asPROVIDER(model)[@<effort>] <time-left>— the model half of the launch-context cluster at the far right of each tab's status row. - An override on any built-in size alias or custom alias takes effect wherever that
alias is resolved. A size-specific phase or task override affects only that alias. An
override on a selector-valued alias — a
|load-balanced pool,||ordered fallback, or parenthesized(A | B) || Clast-resort, such as the shipped size-alias selectors (see the generated shipped size-alias defaults) — suspends that alias's own rotation/fallback for a single concrete target until the override expires or is cleared. - An override on
epic landerorbig epic landeraffects only epic land agents below, or at/above,bead.big_epic_phase_threshold, independently ofdefault modeland of each other.
Every non-default override (an alias, epic lander, or big epic lander) is surfaced
by the alias pill inside the labeled overrides: violet top-bar group: a single active
override renders as overrides: @<alias>[@<effort>] <time-left> or as
overrides: epic lander <time-left> / overrides: big epic lander <time-left>, and
several render as overrides: <first> +N, naming the alphabetically first overridden
alias or launch-setting label and counting the rest. That alias pill sits beside the
priority and disable pills when those are active. In both pills, lane color carries the
"override" meaning while the effort suffix and time use a recessive tone; ∞ means
until cleared. Hover the group for full target and expiry details, or click it to open
Launch Control.
When no override is active, the same status-row chip instead names the current launch
default — the smallest %model value that would pin this exact target (a bare model
name such as grok-4.7 when the model unambiguously names its provider, otherwise the
explicit codex/o3 form) plus the optional [@<effort>] suffix, with no background
accent — and stays live for the whole sase's TUI session. The label is toned with the
launch default's provider: the model name takes the provider's model hue (the same one
the model picker uses, for example Grok cyan, Claude amber, or Codex mint) and the
@<effort> suffix takes a recessive tone from the same hue family, so the two-tone
shape matches the override pills. Provider hue is the calm lane's identity axis, while
the override lanes keep their gold and violet accents for the override meaning. While
the default is still resolving (...) or unavailable, the pill keeps a neutral dim-cyan
tone rather than guessing a provider. A model alias is never used as the pill's subject,
since an alias may be a rotating pool rather than one concrete model. The optional
@<effort> suffix is the launch-effective default a no-%model / no-%effort prompt
will actually receive (alias-borne effort, a temporary default-effort override, or
llm_provider.default_effort); it is omitted when that value is unset. If
llm_provider.default_model (directly or through a referenced alias, such as a shipped
size-alias pool) resolves to a load-balanced | pool, the pill follows the pool's
round-robin cursor as it advances: a launch consumes one member, and within a few
seconds the pill flips to name whichever member runs next. For a cross-provider | pool
the pill's hue flips with it, so the color tells you which provider the next launch
will actually hit. It never resolves on the UI thread and never advances the cursor
itself — it only reflects state that a real launch already changed. Hover the pill for a
<alias> rotates across N models; PROVIDER(model) is next line whenever the default
routes through such a pool. The tooltip deliberately keeps the provider-qualified
PROVIDER(model) form: the pill is the width-constrained surface and stays compact, and
color is never the only carrier of the provider, so hovering is the authoritative
confirmation of which provider the launch default resolves to.
Overrides do not displace explicit launch intent: explicit prompt directives
(%model:codex/o3, %model:opencode/anthropic/claude-sonnet-4-5) and an explicit
provider_name argument always win, already-running agents keep their current
provider/model. A temporary hard provider disable is different: an explicit request
for a hard-disabled provider fails with a provider-and-expiry diagnostic rather than
silently switching providers. A soft disable never fails that explicit request.
Override state is persisted to ~/.sase/llm_override.json — shared across all sase
processes on the machine — and is best-effort self-cleaning: expired or malformed
entries are pruned on next read. Until cleared is a no-expiry mode — convenient, but
still a temporary state, not a permanent config edit. The temporary override is
independent of SASE_MODEL_TIER_OVERRIDE; a concrete override takes the full
provider/model path, while the tier override only applies when no concrete override is
active.
Delegated launches (plan coder follow-ups and sase bead work phase, task, and land
agents) route directly through the
built-in size aliases configured under
llm_provider.model_aliases.builtin, plus the epic lander/big epic lander launch
settings — there is no more role-alias indirection through a separate
default/smart/cheap/etc. lane. A phase or task selects @xsmall, @small,
@medium, @large, or @xlarge by its normalized size directly; a phase or task
without size metadata uses the @small fallback. Epic land agents without an explicit
land model use epic_lander_model (shipped @large) below
bead.big_epic_phase_threshold, or big_epic_lander_model (shipped @xlarge) at or
above it. Because epic lander and big epic lander are configured as raw alias
references, a temporary override on the alias they reference (@large and @xlarge by
default) cascades into their effective resolution; overriding epic lander or
big epic lander directly takes precedence over that nested reference. A temporary
override on a selector-valued built-in size alias — any shipped size-alias pool or
fallback (see the generated
shipped size-alias defaults) — suspends only that
alias's own rotation/fallback and does not cascade to any other alias or launch setting.
Persistent edits¶
Edit and Reset change the alias's value in sase.yml itself, written through the
Rust-backed, source-preserving config-edit path (comments and key order are preserved).
The change is shown in a preview/confirm step before it is written, and after a
successful write the panel offers to commit and push it (y/n). With
use_chezmoi: true the edit targets the chezmoi source and the commit/push runs against
the chezmoi repo followed by chezmoi apply; when the target file is not in a git repo
the commit offer is skipped and the file is simply written. An active temporary override
visually "wins" the effective-target column even after a persistent edit; the state tag
distinguishes the configured value from the currently effective (overridden) one.
Selecting an alias during Edit stores the raw reference (for example, editing
epic lander to @xlarge), so it remains a dynamic link and follows future changes to
@xlarge. Selecting an alias during Override instead resolves it when the override is
written and stores that concrete provider/model snapshot together with the raw token;
later changes to the referenced alias do not change the active override. A canonical
trailing effort is snapshotted with the target and shown in the row, success
notification, and single-override pill (the gold status-row chip for a default model
override, the violet top-bar pill otherwise). A known suffix on an alias reference is
ignored for dependency/cycle checks but retained for the written value; unknown trailing
@token text is not treated as effort.
Builtin aliases edit under llm_provider.model_aliases.builtin.<name>. User aliases
under llm_provider.model_aliases.custom.<name> edit their model field and reset by
deleting the whole custom alias entry. The custom input also accepts a |-separated
load-balanced pool, a ||-separated ordered fallback, or a parenthesized (A | B) || C
last-resort. The editor rejects empty or unparenthesized mixed selectors and alias
references that would reach any nested selector before opening the write preview.
Choosing Pool / fallback... from the e picker opens a guided builder instead of
typing an expression by hand. It seeds its state from the alias's current value — an
existing | or || expression expands into its members and mode, a parenthesized
(A | B) || C value populates the pool plus last-resort tail, a single target becomes a
one-member list, and an empty value starts blank — and shows the live normalized
expression as members change. a adds a pool member through the same model picker and
effort ladder used elsewhere in the panel (its own Custom... accepts a bare model,
provider/model path, or @alias, with an optional trailing @effort); f adds a
last-resort candidate the same way; d removes the highlighted member; J / K
reorder it down and up without crossing the pool/tail boundary; E sets or clears that
member's effort; w / W raise and lower a pool member's weight and ignore last-resort
rows; t toggles between round-robin pool and ordered fallback, and refuses while a
last-resort tail is present; enter confirms and routes the composed expression to the
same preview/write path as a typed value; esc cancels back to the picker. As in the
top-level Edit picker, an alias reference that would reach another pool or fallback is
unselectable here. Confirm is blocked, with an inline reason, while the selector has
fewer than two pool members or the live validation line reports an error.
Examples¶
- Highlight
default model,o, pickcodex/o3, duration1h— launches with no%modeldirective use Codexo3for the next hour, then revert to the configured default. - Highlight
@medium,o, pick a model, thent, enter5pm— the preview resolves the next 5:00 PM in the configured timezone and the override expires at that exact instant. - Highlight
@small,o, pickclaude/opus, and chooseUntil cleared— small phases and tasks without an explicit model use CLAUDE(opus) until you clear it; the violet non-default pill appears in the top bar. - Highlight
@large,e, pickclaude/opus, and confirm — only large phases and tasks without an explicit model use that target, replacing the shipped pool (see the generated shipped size-alias defaults); other-sized phase/task routing is unchanged. - Highlight
@xlarge,e, pickclaude/opus, and confirm — xlarge phases and tasks use that target directly, andbig epic lander(left at its shipped@xlargereference) inherits the same change. - Leave
@xlargeimplicit — xlarge phases, tasks, and threshold-selected epic landers (which reference@xlargeby default) select the first available member of its fallback chain (see the generated shipped size-alias defaults). - Highlight
@xsmall,e, chooseCustom..., enterclaude/haiku@minimal | codex/gpt-4.1-mini@low, and confirm — xsmall phases and tasks round-robin across installed providers while the panel continues to show the next selection without consuming it. - Highlight
@small,e, chooseCustom..., enterclaude/haiku | codex/gpt-4.1-mini, and confirm — small phases and tasks round-robin across this independent pool without consuming the@xsmallcursor. - Highlight
@medium,e, chooseCustom..., enterclaude/haiku@minimal | codex/gpt-4o-mini, and confirm — medium phases and tasks round-robin across this independent pool without consuming the@xsmallor@smallcursor. - Press
t— open tmux Agent, thencor Enter on Claude Code to launch it in a newaiwindow;slaunches once without approval-bypass flags. - Press
p, highlightclaude,d, choose1h— new alias-backed launches route around Claude for the next hour, direct%model:claude/opuslaunches fail explicitly, and already-running Claude processes continue. - With
claudedisabled, an override on@mediumthat targetsclaude/opuspauses; the row shows the live fallback target until Claude is re-enabled or the disable expires. - Highlight
big epic lander,e, pick a model, and confirm — only threshold-selected epic landers use that persistent target; leaving it implicit inherits through its shipped@xlargereference, independently ofepic lander. - Highlight
big epic lander,e, filter for@large, select it, and confirm — the persistent value is the dynamic@largereference, not a copied concrete model. - Highlight
@medium, presso, select@xlarge, then choose1h— the override records the concrete provider/model to which@xlargeresolves at write time while retaining@xlargeas its raw input. - Highlight an alias or launch setting,
x— clear its temporary override;r— unset its configured value back to its implicit fallback.
See docs/llms.md for the resolution order and state-file format.
Notifications Modal¶
Press i to open the notifications modal. On the Agents tab, Enter opens the selected
agent's pending gate or Patch directly, without the modal. See
docs/notifications.md for the full keybinding reference, modal
tabs, priority/error/muted classification, and the per-notification snooze and mute
affordances.
Rows and the detail header begin with the notification's single-glyph icon when one is present, with a per-action fallback icon otherwise. The text action badge remains visible as the secondary label.
Press d on the highlighted inbox row to open Gate Debug, even when the row is not
gate-backed or its gate modal can no longer load. The same d binding is available
inside plan/epic approval, user-question, launch-approval, custom-gate, and workflow
HITL panels. Gate Debug presents Overview, Request, Response, Errors, and raw Row tabs;
[ / ] switch tabs, y copies the current tab, Y copies the bundle path, e opens
the backing file, and d, q, or Esc closes the overlay without losing state in the
underlying panel.
The inbox: top-bar group renders one colored <icon><count> chip per
notification-panel tab, in the panel's own order and colors, or a dim inbox: 0 when
nothing is pending. See Top-Bar Indicator for the
snoozed-only and overflow forms.
Snooze Reminder Scheduling¶
Snooze expiry does not depend on the general refresh cadence. sase's TUI keeps at most
one timer for the nearest deadline reported by the current notification snapshot, so
reminders fire on time even with clean inotify state or --refresh-interval 0 (which
disables ordinary auto-refresh). The timer callback stays thin and synchronous: it
compares cached wall-clock values on Textual's message pump and hands the store read to
a coalesced proc, so no disk or worker I/O runs on the pump and an expired snooze never
triggers a full Agents-list rebuild.
While any snooze is pending, sase's TUI rechecks the wall clock at most one second apart, so a suspended host, a resumed session, or a forward/backward system-clock change re-evaluates the authoritative UTC deadline promptly instead of waiting out a monotonic timer. Startup reconciliation, notification-file watcher events, ordinary polling, and modal snooze/resnooze/unmute/dismiss completions all route through the same coalescing guard, so an external mutation can replace or cancel the cached nearest deadline immediately. The coordinator starts after first paint and its timer and task are cancelled during normal and controlled teardown.
Once due, sase's TUI performs one current-state snapshot read, applies counts, toasts,
and status projections, then schedules the next future deadline. Each observed resurface
batch produces toasts (one per row, or grouped per severity when more than three rows
arrive together) and at most one tmux bell, unless a
delivery rule overrides it — including rows that were
marked read while snoozed — and no repeat on later polls. Cancelled, dismissed,
permanently muted, and not-yet-due rows never ring. If another process wins the expiry,
the persisted unread state and resurfaced_at still make the transition observable
here. Resurfaced rows sort as recent activity in the modal while continuing to display
their original sent time. See
docs/notifications.md for the full
state and timing contract.
Notification Actions¶
Some notifications carry an action field that triggers a handler when the notification
is selected. The following notification action types are supported:
| Action | Source | Behavior |
|---|---|---|
CustomGate |
Agent/tool | Opens the generic choices, add-ons, and feedback modal |
GateExecutionFailed |
Gate executor | Opens the resume / restart / cancel dialog, or the error report when none applies |
HITL |
Workflow | Opens the workflow human-in-the-loop response modal |
JumpToAgent |
Agent/workflow | Jumps to the matching Agents-tab row, revealing it first like the Node Finder: expands collapsed folds, grouping banners, and panels; shows I-hidden rows; clears a hiding Agents query with a toast |
JumpToPatch |
Sync/workflow | Jumps to the referenced Patch on the Patches sub-tab |
JumpToMentorReview |
Mentors | Jumps to the Patch and opens mentor review output when available |
LaunchApproval |
Agent | Opens the launch approval modal for an agent-requested launch |
OpenToolRun |
Tool run | Selects the settled run's Agents-tab node with its ⚒ Runs card active and the run's block selected, or opens Admin Center → Tools focused on the run |
PlanApproval |
Agent | Opens the plan approval modal |
RemoteAttention |
Remote machine | Opens the remote question or gate modal and submits to the owning machine |
SudoRequest |
Agent | Opens the sudo review modal; approving runs in a terminal |
Tmux |
External bridge | Runs tm <workspace-name> for the notification's action_data.workspace_dir |
UserQuestion |
Agent | Opens the structured user-question response modal |
ViewErrorReport |
Axe/agent | Opens action_data.error_report_path, or the first attached file, in $EDITOR |
The axe error_digest job creates ViewErrorReport notifications whose digest files
live under ~/.sase/axe/error_digests/digest_<timestamp>.txt; user-agent failures can
use the same action for their own attached error reports.
The custom-gate modal shows the sender and notes or verified preview, one icon-led
button per terminal choice, and checkboxes for that choice's independently selectable
add-on commands. The Decision column holds those controls only; an option that declares
typed input wears a dim ✎ n inputs badge and collects those values in the dedicated
input panel instead (see Remapping Gate Modal Keys).
Required feedback also opens that panel, whose submit stays blocked until non-empty text
is present; optional feedback still answers in one keystroke and is attached by pressing
i first, and disabled feedback shows no note field at all. Unsupported future actions
produce a warning instead of silently doing nothing.
Custom gates and neutral HITL gates execute through the shared hash-verifying gate executor. sase's TUI schedules the terminal command and each selected add-on through the tracked proc queue, streams live stdout/stderr to the proc, shows each command as a reporter phase, and refreshes the inbox when the proc completes. Legacy HITL bundles retain the direct response-file fallback.
Toast Notifications¶
Each newly-arrived notification produces a short toast in the TUI. The toast text is derived per-action type (plan, question, HITL, axe error, Patch sync, agent update) so the message previews the actual event rather than a generic "N new notification(s)" line. Severity is also picked per type: plans, questions, and HITL render as warnings; axe errors (and sync failures) render as errors; everything else renders as information.
A genuinely new tale or epic plan review rings the terminal bell on arrival (one bell announcement, sent as three short tmux beeps) and remains visually prominent as a warning toast and priority inbox row. Already-answered plan reviews discovered during polling and the post-approval coder or epic handoff stay silent. Questions, other audible notification classes, and explicit snooze-expiry reminders retain their existing bell behavior; snoozing a plan review therefore still produces the requested reminder bell when it expires.
These are defaults. ace.notification_rules can
suppress a toast or replace the bell with a sound file or silence for any notification,
including snooze-expiry reminders; rows whose toast is suppressed are left out of the
grouped-toast count.
When more than 3 notifications arrive in the same poll tick, per-notification toasts are
consolidated into one grouped toast per severity bucket (e.g.,
2 warnings: 1 plan, 1 question). Ordering is urgency-first: errors, then warnings,
then information. Silent notifications are excluded from this pipeline entirely.
Agent completion and failure toasts include the %id-set agent name with an @ prefix
when present (e.g., CLAUDE(opus) @sase-q.land completed: ace(run)-...); anonymous
agents (no agent_name) keep the prior format.
Macro Browser¶
Press # on any tab to open SASE Admin Center, press 1 for Config, then
choose the Macros child (0 then 7, or [ / ]; within a session, Config
reopens on the child you used last). It displays all discovered macros in a two-panel
layout: a filterable list on the left and a syntax-highlighted preview on the right.
Markdown macros with leading YAML frontmatter render the frontmatter and body with their
respective syntax styles.
Macros are grouped by source (project sase/macros/, home ~/sase/macros/,
project-specific home, config sase.yml, plugins, built-in, plus labeled legacy
compatibility sources). Workflow macros (multi-step YAML) are marked with a gear icon;
standalone workflows are displayed with the #!name insertion syntax. Project-local
macros defined in each project's sase.yml file are also included, even though the
TUI's normal config loading does not read project-local config files.
The list rows and preview metadata show the same insertion form and visible input
metadata used by Ctrl+T completion. Step-only inputs are hidden from this user-facing
surface because they are supplied by workflow execution rather than typed by the user.
Keybindings¶
| Key | Action |
|---|---|
j / ↓ |
Navigate to next macro |
k / ↑ |
Navigate to previous macro |
Ctrl+N |
Navigate to next macro |
Ctrl+P |
Navigate to previous macro |
' |
Jump to a non-header row via adaptive hints |
/ |
Show and focus the filter input |
[ / ] |
Switch to the previous / next Config child |
Ctrl+D |
Scroll preview panel down |
Ctrl+U |
Scroll preview panel up / clear input |
Enter |
Target the highlighted macro: load it into the home prompt bar for editing |
E |
Open the highlighted definition in $EDITOR |
Ctrl+O |
Add a new macro |
Ctrl+I |
Inline-expand the highlighted macro into the home prompt bar |
Esc |
Close SASE Admin Center |
The filter input starts hidden. Press / to reveal it, then type to narrow the list in
real time; Enter or Esc closes the input and returns to the list. While the input is
focused, every printable key — including digits and ' — is ordinary filter text, so
values such as bug2 can be typed normally; Ctrl+N / Ctrl+P, Ctrl+D / Ctrl+U,
Ctrl+O, and Ctrl+I still reach the list. From the list itself, ' arms entry-jump
over the non-header rows.
Editing Macros¶
Press Enter on any macro to load its definition into the home prompt bar and target it
for editing — see
Editing an Existing Macro from the TUI for
the full targeting loop, including the visual chip states, the target-aware Enter save
menu, and the chezmoi-aware write path. Project, home, and config sources are editable
and bind the bar to their source file. Read-only sources (legacy, plugin, and built-in)
load without a target: the bar shows a persistent read-only marker instead, and gw
falls through to the save-as flow so your edits land in a new, editable copy rather than
being silently discarded. Press E to open an editable definition directly in $EDITOR
instead; after saving, the browser offers the applicable follow-up actions (commit/push,
a scoped chezmoi apply, or sase memory init / sase skill init).
Creating Macros¶
Press Ctrl+O to start the guided creation flow:
- Location modal — Choose where to save the new macro (project
sase/macros/, home~/sase/macros/, projectsase/sase.yml, or a global config file). Legacy sources remain browseable but are never new-write destinations. PressCtrl+Gto open the selected config file in$EDITORinstead of proceeding with creation. - Filename modal — Enter a filename (
.mdfor prompt parts,.ymlfor workflows). Workflow files are pre-filled with a YAML template containing the workflow scaffold. - Editor — The file opens in
$EDITORfor editing. - Follow-up actions — After saving, the browser offers the actions that apply to
the new file: commit/push, and — when
use_chezmoiredirected the write to the chezmoi source — a scopedchezmoi applyof just that file.
Jump All Modal¶
Press ` (backtick) on any tab to open the Jump All Modal. It displays all entries
across Agents, Artifacts, and Services tabs with the same adaptive one- or two-character
hints used by current-tab entry jump. Completing an entry's hint switches to the
appropriate tab and focuses it.
Up to 62 entries use 0–9, a–z, A–Z. Larger result sets use fixed-width pairs
from 00 through ZZ; a first character is consumed without closing the modal, and
uppercase characters remain case-sensitive.
| Key | Action |
|---|---|
| Hint | Jump to the corresponding entry |
` |
Jump back to the previous position (see below) |
Ctrl+D / Ctrl+U |
Scroll the entry list down / up |
Esc |
Close modal (any other key that is not a hint also does) |
The modal groups entries by tab (Agents, Artifacts, Services) and shows contextual information for each: PR names and statuses, agent names with running indicators, and Axe routine/command labels.
Jump Back¶
Both jump modals support a jump-back feature for toggling between two entries:
- Backtick jump-back: Pressing
`inside the Jump All Modal returns to the previous position, enabling quick toggling between two entries across tabs. - Apostrophe jump-back: Pressing
'twice ('') in the single-tab entry jump mode jumps back to the previously jumped-from entry. The footer shows a "JUMP" mode indicator with' backwhen a target exists. - Fast jump:
Ctrl+Oruns the same current-tab jump-back path without painting hints first; when no jump-back target exists, it selects the first current-tab hint. - Forward jump: After walking backward,
Ctrl+Shift+Owalks forward through that current tab's jump stack. Agents, Artifacts/Patches, and Services keep independent back and forward positions.
The single-tab variant (' apostrophe) shows entries only from the current tab with the
same hint-character navigation.
Node Finder¶
Press " (quotation mark) on the Agents tab to open the Node Finder modal. It lists
every reachable sase node as a tree — clans, agent nodes, session turns (including
monitors and gates), workflow roots, workflow agent steps, and stand-alone named procs
— including rows hidden by collapsed folds, collapsed grouping banners, collapsed or
isolated tribe panels, and the Agents query. Every jumpable row carries a jump hint.
| Key | HINTS mode (default) | SEARCH mode (query focused) |
|---|---|---|
0-9a-zA-Z |
Complete a hint and jump, or set a pending prefix | Edit the query; live refilter |
Tab / Shift+Tab |
Focus the query | Back to HINTS, query kept |
/ |
Focus the query | Types / |
Enter |
Jump to the highlighted node | Jump to the highlighted node |
Ctrl+N / Ctrl+P, ↓/↑ |
Next / previous jumpable row (wraps) | Same |
" |
Jump back (same as Ctrl+O) |
Types " |
Backspace |
Cancel a pending prefix; otherwise swallow | Delete a character |
Esc |
Cancel a pending prefix; otherwise close | Back to HINTS, query kept |
PgUp / PgDn |
Scroll the preview pane | Scroll the preview pane |
| Any other key | Swallow; the footer flashes no hint ‹x› |
Types |
The why-hidden glyph legend: blank means visible, ◆ you are here, ⊘ hidden by the
Agents query, ◌ hidden by I (until listed, shown as an I hides K chip), ▭ inside
a collapsed or isolated-away tribe panel, ≡ inside a collapsed grouping banner, ▸
inside a collapsed fold.
"Hidden" covers folds, banners, panels, and rows the Agents query hides. Jumping to a
query-hidden node clears the Agents query (recorded in query history, announced with a
toast); restore it with the query-history keys. The one-line jobs of the sibling
surfaces: ' jumps by hint on the current tab, ` jumps across all tabs, digits
jump roster members, and " in the finder jumps back.
The preview sits beside the list. On a terminal at least 140 columns wide the list also
keeps a status column. From 100 columns up to that width the two panes split evenly and
the status column is hidden. Below 100 columns the preview stacks under the list. PgUp
and PgDn scroll the preview in both modes.
The preview paints from the snapshot taken when the finder opened. The highlighted row
shows its kind and name, a compact identity block, and a breadcrumb from the tribe panel
through ancestor names to the row (@default ▸ parent ▸ name). It also says whether the
row is visible, and what Enter will do: ⏎ selects it when the row is already
visible, or the reveals it will perform first (clearing the Agents query, opening a
hidden panel, opening a collapsed group, expanding folds) and then selecting it. A
session row lists its turns. A clan lists a status tally and up to twelve members, then
… N more members when the clan is larger. A monitor or named proc shows its command
and an output tail. A gate turn shows its label and state. A workflow root shows a
step-status tally. A non-node row says Select a node to inspect it.
Agent turns then fill two more sections from that turn's artifacts: a PROMPT head and
a REPLY · tail. A session container uses its newest agent turn for those sections and
labels the prompt with that turn's name. Clans, monitors, gates, and named procs stop
after the in-memory preview. The prompt and reply slots read ⋯ loading until that read
finishes, then (no prompt recorded) or (no reply recorded) when the artifact is
empty. The reply tail names how many earlier lines were left off.
Mentor Comment Stats in PR List¶
When a Patch has completed mentor reviews with comments, its Patches sub-tab list entry shows inline stats:
- checkmark + count (e.g.,
✓3) — number of accepted comments - dot + count (e.g.,
●2) — number of unread comments
These stats are computed from the latest stitch's finished mentors. They update as you accept or read comments in the Mentor Review modal.
PR Origin Chip¶
A Patch with a pr_url shows a PR_ORIGIN chip next to its PR badge in the Patches
sub-tab list and in the detail panel: nothing for the default sase origin (a PR SASE
created through the tracked PR workflow), external for a PR SASE adopted but did not
create, and origin? for unknown (no evidence either way). The detail panel adds a
one-line note for external Patches, since AXE excludes external-origin Patches from
its candidate selection entirely (see AXE). Press !o on a PR row (see
PR Actions above) to open the Mark PR Origin modal and set it explicitly,
or run sase patch set-origin <name> <sase|external|unknown> (see
CLI Reference). See
PR_ORIGIN and
Origin Matching for the underlying Patch field and
the origin: query property.
Current project¶
sase's TUI has one current project: the head of the VCS macro MRU store. Launching
an agent on a project — or on a Patch owned by that project — promotes it to that head.
sase project set-current <project> and the Projects tab's c key (see
Projects Tab) move it the same way, by promoting the project to the MRU
head, without a launch. Click the status-row project: +<name> chip to open the +
launch picker, which is the surface that actually records a launch.
Because setting the current project and launching an agent both promote the same MRU
entry, making a project current also moves it to the head of the prompt bar's <ctrl+p>
VCS-prefix cycle — the same coupling a launch already produces.
The chip is the project half of the launch-context cluster at the far right of every
tab's status row, which reads model: <launch default> · project: +<name>. The dim
model: label reads override while a default model override is active, and when the
row is too narrow both labels drop, leaving <launch default> · +<name>. The
· project: group disappears when no current project resolves or the indicator is
disabled. On the Agents tab, the runner load gauge sits just before
the cluster. The chip's color is unique among currently enabled projects. Hovering names
the project, the MRU ref it came from, and (when the head was a Patch) the Patch; the
tooltip also names the two ways to make a project current: launch an agent on it, or
press c on the Projects tab. Hide the chip with
ace.current_project.indicator: false.
When ace.current_project.seed_filters is on (the default), first-open surfaces that
can filter by project seed from the current project instead of starting at all projects:
- the shared Artifacts project scope (Agent, Stitches, Beads, Plans, Files, Patches)
- the Statistics project filter
- the Repos / Workspaces inventory filters
- the Memory panel's scope ring
- the highlight in the
+launch picker
The seed never overrides an explicit project: / +name term, a pick you already made
this session, or a surface that is already open. A mid-session launch or set moves the
chip live but does not re-scope those surfaces. Turn seeding off with
ace.current_project.seed_filters: false.
The Agents-tab search query is not seeded by default
(ace.current_project.seed_agents_query: false) because that query also drives unread
jumps and prospective clans, not just the visible list. When enabled, the seed is used
only if there is no remembered Agents query; a remembered empty query restores the
unfiltered view and suppresses the seed. One line of config turns seeding on.
Inspect the resolved project from the CLI with sase project current. See
ace.current_project for the three fields.
Tab Bar Display¶
The tab bar renders plain tab labels (Agents, Artifacts, Services). Per-bucket
counts live inside each tab's body — for example the per-panel count summaries on the
Agents tab — rather than as suffixes on the tab title itself.
Top-Bar Indicators¶
The right-aligned indicator cluster speaks the same visual language as the status-row
cluster beneath it (load: 5/8 · model: opus@high · project: +sase): every group
renders as a <type>: <body> group where only the <type>: label is dim, so a group's
value looks the same in full and compact modes, and visible groups are joined by a dim
·. The left-to-right order runs from activity to system state to launch routing to
personal queues: tools, bg, updates, overrides, priority, disabled, stash,
inbox. The always-visible inbox anchors the right edge directly above project:.
Labels are fixed strings that never pluralize. Count chips carry their identity glyph
inside the fill — ⚒ for tool runs (sky blue, red with ⚠ while silent), ⚙ for
background procs (blue) and bare monitors (orange), ⬆ for updates, ≡ for the stash,
★ for priority — so compact mode (labels dropped together when the full cluster does
not fit in the cells left over after the tab strip and a 2-cell minimum gap; separators
kept) still identifies each group; widening restores full labels without oscillation.
Every group is clickable: tools opens Admin Center › Tools › Runs with all projects, bg
opens the Admin Center Procs tab, updates opens the Updates tab (or the Procs tab on the
running update while SASE is updating, when the badge shows its green gear inset, or the
Procs tab on the first restart blocker while a restart is queued, when the badge shows
its yellow gear inset), overrides, priority, and disabled open Launch settings, stash
opens the Prompts overlay on Stash, and inbox opens the notification modal. While SASE
is updating itself, the updates group shows a green ⚙ gear inset at its left edge.
While installed code waits for TUI-local tasks, submissions, or installation changes
before restarting ACE and the SASE service, it shows a yellow ⚙ gear inset instead
(green outranks yellow). Independent commands keep running. Durable operations started
from this TUI still finish their result handling first, within the same 60-second wait.
The yellow tooltip names the actual blockers and the time ACE restarts anyway; clicking
it opens the Procs tab on the first blocker. Feature-flag restarts never show the yellow
gear.
Tools and Background Indicators¶
The tools: group shows every live sase tool run on the machine: a sky-blue ⚒ N
chip for healthy runs, a red ⚒⚠ M chip for silent runs (no activity for 60 s or more),
and an orange ⚙ K chip for monitor turns not currently carrying a run. The ⚒ count
comes from the ToolRun ledger, so it covers every mode (escalated, detached, catalog,
monitor adopt/join, foreground) on every tab, ignoring the project filter, tribe
selection, and session; a live child folds into its live parent. An execution is drawn
exactly once: a proc that carries a live run is never drawn as a gear. Each chip hides
at zero and the group hides only when all chips are zero. Before the first glance load
the ⚒ chips stay hidden rather than showing a false zero; if loads keep failing the
last snapshot renders dim with a trailing ?. Hover lists up to five runs (silent
first, then oldest) plus bare monitors; click to open Admin Center › Tools › Runs with
all projects.
The bg: group shows a filled blue ⚙ N chip while sase's TUI own background procs are
running (e.g., sync, mail, accept, and notification-gate operations) — the same count
the Procs tab header shows in blue. Tool-run carriers never count here; they live under
tools:. Procs that are updating SASE move to the green ⚙ gear in updates: and no
longer count in the blue chip. The chip hides at zero. The group excludes service-host
rows — service procs and oneshots — which the Services tab reports instead. Hover for
the proc labels; click to open the Procs tab.
Current Project Indicator¶
The uniquely colored +<project> chip is not in the tab bar: it is the project half of
the launch-context cluster at the right of each tab's status row
(model: <launch default> · project: +<name>). Clicking it opens the + launch picker.
See Current project for what the chip means, what it seeds, and how
to turn each part off.
Runners Modal¶
Press ,R (leader + R) to open the runners modal. It shows concurrency information
including hook runners, agent runners, and a Procs section listing active and
recently completed TUI procs from the current sase's TUI session. These include Patch
actions, agent launch and cleanup work, monitor-stop, and notification updates. Each
row shows the target, proc type and status, and elapsed or total duration; a failed row
also shows its error message. This modal does not show proc output. Use the Admin
Center's Procs tab or sase proc show ID for durable records and captured
output. Press j to paint hint keys over the rows and jump to the matching agent or
Patch; Ctrl+D / Ctrl+U scroll, and r, q, or Esc closes the modal.
File Panel Rendering¶
Agent files render in full and scroll natively in the file panel. Syntax highlighting
falls back to plain text for large content. Pathological outputs above the file-panel
safety limit show the first 5,000 lines and an explicit editor notice; press E to open
the complete content.
Agents Zoom and Node Rail¶
The left column has three presentations, derived from deck-area state: EXPANDED by
default, RAIL when the persisted Ctrl+S preference is set, and HIDDEN while a deck
is zoomed (HIDDEN wins over RAIL). The persisted preference only ever means rail;
zoom never writes it.
Ctrl+S toggles the node rail: the same tribe panels and the same visible rows at a
fixed 22-cell width, row for row, so a row never moves vertically. Each agent row
carries its glyph plus its name: the glyph cell (? needs you, ✗ failed, ▶ running,
◐ starting, ○ queued, ◷ waiting, ✓ done, Ø user-stopped, plus kind glyphs for
monitors, gates, workflows, steps, and Patches) followed by the expanded row's identity
token. Nested names render relative to their parent (.cld, --plan), long names use a
middle ellipsis, and the width never fits to content, so the deck edge stays stable.
Color means urgency; the rail shows no runtimes, badges, or emoji. Settled-and-read
names render dim. Tribe titles are left-aligned (up to 18 cells), and the bottom border
counts off-screen rows (▴N ▾M).
Position is identity: rail row k is expanded row k. Hovering a rail row shows that
row's full expanded text as a tooltip, the identity header always names the selection
(so j / k is never blind), and ' jump hints plus the " node finder reach every
node. Focus stays on the list in rail mode, so every key works unchanged; runtime ticks
pause and catch up on expand. The footer shows Ctrl+S expand nodes while railed, and
the info row's nodes i/N · Ctrl+S chip is clickable. The ? help modal's Node Rail
box lists the full glyph legend.
Each rail agent row ends with a right-aligned ×N count in the shared count column and
a pip at the edge. ×N means "folded, N inside": it shows only when the expanded row
shows ×N, capped at ×99. The trailing pip cell shows the marked ▪ pip when marked,
the unread • pip when unread and unmarked, and blank otherwise — marked always wins
over unread. Tree guides (│ continuing, └ terminal) prefix child rows and clamp at
depth 3. Banners keep the expanded prefix vocabulary with the label and a heavy (L0) or
light rule that is always at least 1 cell; folded banners add the ×N count and a
1-cell urgency roll-up (? needs-you beats red ✗ failed beats gold • unread, else
blank). Collapsed tribe titles add their ×N lane roll-up the same way.
Z zooms the focused deck panel in place: it snapshots the deck-area state (layout,
panels, focus, ratio), then shows only the focused panel with the node column hidden
entirely — no rail. The zoomed panel gets a heavy border in its own deck accent, a
reverse-gold ZOOM chip leading its title, and a restore hint (◧ 1 of 2 · Z restore
from a split, Z restore from a single deck) leading its subtitle. The info row shows
the same clickable ZOOM chip, then Z restore, then node i/N (j / k still moves
the selection); the footer shows Z restore while zoomed. The panel keeps its widget,
card, and scroll position, and search, E, cards, and decks all work normally while
zoomed.
A second Z restores the snapshot exactly, as does Ctrl+S while zoomed ("in zoom, any
sidebar key gives your layout back"). Using a layout key (\, |) while zoomed only
restores the snapshot, keeping deck and card edits made while zoomed, so no split is
ever silently lost.
Image Preview Foundation¶
sase's TUI renders PNG, JPEG, WebP, and GIF attachments with a Pillow-backed Rich cell preview. The renderer decodes the first image frame, preserves aspect ratio within the visible panel bounds, composites transparency, and paints colored half-block cells using truecolor when the terminal advertises it and 256-color approximations otherwise.
Generated images are already attached to successful agent completion notifications and
recorded in done.json as image_paths. The Agents tab file panel and notification
modal route supported raster image attachments through this preview layer before
attempting text decoding. See agent_images.md for supported image
extensions, guardrails, and current preview behavior.
Agent Auto-Naming¶
Prompts with no %id directive, or with a bare %id, use the plain auto-name template
@. SASE reserves the lowest available token from the sequence 0, 1, ..., 9, a,
..., z, 00, 01, ...; with no reserved names, plain auto-naming yields concrete
names such as 0, then 1.
An explicit %id value containing exactly one marker is an agent-name template. The
legacy marker is bare @, so the first allocation for %id:@.cld becomes 0.cld,
%id:build-@ becomes build-0, and %id:research.@.final becomes research.0.final.
Keyed markers such as %id:research.{@1}.final are preferred for macro swarms: SASE
resolves every matching key in %id, %clan, clan=, waits, fork/resume references,
and prose before any spawned member can start. Bare @ still works, but template
references use latest-wins lookup and can be unsafe when a swarm member starts after a
newer overlapping launch. See Macro template directives for
{@<id>} and {@<id>!} qualification rules.
Names are permanent IDs: a name used by any existing agent state remains reserved until
that agent is explicitly wiped or deleted. This enables the fork-by-name workflow: press
F on a running named agent to queue a follow-up that waits for it to finish and then
loads its conversation history.
Provider/Model Suffixes¶
When the same base name is shared by multiple co-launched agents (e.g. multi-model
fan-out via the %model: directive), the rendered display name carries a short
.<provider> or .<provider>(<model>) suffix so each row is distinguishable. Provider
suffixes are supplied by the LLM provider plugins via the llm_provider_short_name hook
(built-in defaults: cld for Claude, cdx for Codex, agy for Antigravity).
Additional provider plugins can contribute their own short names. Model-name shorthands
come from the llm_model_short_aliases hook (e.g. fable for claude-fable-5,
gpt61sol for gpt-6.1-sol; see Model Short Aliases)
and are resolved against the configured model so the suffix stays compact regardless of
how the model was spelled in the prompt or config. Single-runtime spawns omit the
suffix.
An explicit %id:<name> launch fails before spawning if <name> is already reserved.
The prompt is saved as a cancelled history entry and the error suggests the lowest free
numeric suffix, such as <name>1. To deliberately reuse a reserved name from the TUI,
launch with %id:!<name>; the ! form confirms that SASE should wipe the previous
owner and then claim the name for the new agent. Reviving and dismissing agents preserve
their stored names.
The durable registry lives at ~/.sase/agent_name_registry.json and is rebuilt from
visible artifacts plus dismissed bundles when missing or stale. Use
sase agent names migrate-auto to run the historical auto-name migration that moves
older generated names into the permanent namespace; pass --force to rerun after the
migration marker is present or --json for machine-readable output.
Per-Step Naming for Multi-Agent Workflows¶
Sequential plan-session workflows have a stable session container plus member suffixes.
When the first follow-up attaches, the original agent is renamed and the bare session
name becomes a pure container. Generated follow-up rows and phase metadata use canonical
double-dash suffixes. For example, if the initial agent was named a:
- The first attachment creates session container
aand gives the original its persisted role suffix (a--planfor a plan proposer ora--0for a generic agent). - The planner phase uses a canonical
--planrole suffix. - Feedback and question-continuation rounds become
a--2,a--3, etc. - Terminal follow-ups use the phase suffix, such as
a--code,a--epic, ora--commit.
The base name (a) is reserved for the session as a whole, so %wait:a or @a
references resolve through the session container. In sase's TUI, the aggregate session
row displays that bare container name, while expanded concrete member rows keep their
exact suffixed names (a--0, a--plan, a--code, and so on). New plan-session
metadata stores double-dash role_suffix values (--plan, --2, --code, ...).
sase's TUI still canonicalizes older dotted suffixes (.plan, .2, .code, etc.) and
legacy single-dash suffixes (-plan, -2, -code, etc.) when reading legacy
artifacts.
Agent Statuses¶
Each row in the Agents tab displays a status label. The sections below separate labels that represent execution, waiting, review, or a successor handoff from labels that represent completed work. This is a display-oriented grouping: a gate turn can be processless or already settled while its label still uses the Running bucket to represent an in-progress handoff.
Execution, Waiting, Review, and Handoff Statuses¶
Gate-turn rows use lifecycle presentation rather than a separate hard-coded color for every plan or question label: pending/settling labels use the gate's configured or deterministic accent, answered/completed/stopped labels turn grey, and failed/timeout/lost labels turn red. Legacy non-turn rows retain their older status presentation where noted, with specialized lifecycle labels falling back to dim text. Presentation and bucket are separate: a grey settled gate can still appear in the Running bucket when it handed off to a successor.
| Status | Presentation | Description |
|---|---|---|
| RUNNING | Gold | Agent subprocess is executing |
| QUEUED | Cornflower blue | Cleared dependency, bead, and time waits; parked for runner capacity |
| WAITING | Amethyst/purple | Paused on a dependency, bead, or time wait; ?N marks unknown targets; matching bead tokens follow agent tokens |
| WAITING INPUT | Gate accent; legacy dim | Workflow is paused at a human-in-the-loop (HITL) step |
| TALE | Gate accent; legacy dim | An authored tale is waiting for user review |
| EPIC | Gate accent; legacy dim | An authored epic is waiting for user review |
| PLAN | Gate accent; legacy dim | A legacy or unreadable-tier plan is waiting for user review |
| PLAN APPROVED | Grey on the settled gate turn | Plan approval was durably accepted; follow-up execution is being attempted |
| TALE APPROVED | Grey on the settled gate turn | Tale approval and commit were durably accepted; follow-up execution is being attempted |
| EPIC APPROVED | Grey on the settled gate turn | Epic approval was durably accepted; creation is being attempted and no epic ID has been back-filled yet |
| QUESTION | Gate accent; legacy dim | Agent is asking the user a question (via /sase_questions) |
| ANSWERED | Grey on the settled gate turn | The answer was accepted and a successor is being launched |
| SUDO | Gate accent | An agent's sudo request is waiting for review; it settles as SUDOED or DENIED |
| RETRYING | Orange | Agent hit a retryable error and is in a countdown before retrying |
Modern /sase_questions calls hand the session to a processless QUESTION gate turn
and end the asking LLM turn. The turn owns the durable question until it is answered,
cancelled, or times out, so dismissing its notification does not dismiss the session
state. An answer settles it as ANSWERED and launches the next ordinary session member
with the accumulated Q&A. That successor starts under the serial-session admission
exemption and becomes the session's occupied slot; it does not enter the runner queue.
The Enter shortcut can reopen the live question even when no unread notification
remains.
Approval labels are receipt-derived: they appear as soon as the accepted gate decision
is durable, without waiting for the selected command or successor launch to finish. If
that execution fails, the label changes to PLAN FAILED or EPIC FAILED, so an
accepted decision cannot hide a failed action. Commit-only plans are different:
PLAN COMMITTED appears only after the archive succeeds.
Older in-flight runs may still use a pending_question.json marker. Those compatibility
runs yield their slot while unanswered, then reacquire capacity in the same process and
can become QUEUED after an answer. sase's TUI continues to project that marker as
QUESTION until the legacy continuation resumes or terminates.
The keybinding footer renders available conditional actions as non-breaking key/label
chips. When the chips do not fit on one line, the footer switches to a deterministic
grid so narrow terminals and leader-mode action sets do not wrap in the middle of a
binding. Mode labels such as LEADER are pinned on the left, and the service status
indicator remains pinned on the right. The status is a segmented badge with a neutral
SVC label chip before the colored state chip, so the indicator always identifies the
service host it describes.
Service Health Pill¶
Once the first service-status snapshot loads, the SVC pill summarizes service health.
Proc states use the wire vocabulary the core emits (running, unavailable,
disabled, stopped, crash_loop, backoff, exited): crash_loop and backoff
are failures, exited is a failure while desired state is running (a clean give-up
reads as a warning), unavailable is a warning, and disabled or an operator-stopped
proc is muted rather than a failure.
| Pill | Color | Description |
|---|---|---|
N/M |
Teal | Healthy: N of the M counted service procs are running (enabled procs whose desired state is running or that are unavailable) |
! |
Red | Unhealthy: the service host is stopped or stale, or a counted proc is unavailable, crash-looping, backing off, or exited |
? |
Yellow | Unknown: no service-status snapshot could be read (service status unavailable) |
When health is unhealthy, sase's TUI also raises a Services unhealthy: <reason>
warning toast (for example host stopped, host stale, or gateway unavailable), and
raises it again whenever the underlying service status changes while health stays
unhealthy. A host that is still starting is not a failure; a host whose heartbeat is
stale is a failure distinct from stopped.
The health pill replaces the host-state labels below as soon as the first status load
finishes. When no snapshot could be read the pill shows a yellow ? (unknown) instead
of a healthy count. The labels are therefore only visible briefly at startup, between
the startup stopwatch retiring and the first status load; -R / --restart-service
restarts the host after that load, so the pill keeps showing health rather than
RESTARTING:
| Status | Color | Description |
|---|---|---|
| RUNNING | Green | Service host is running normally |
| STOPPED | Red | Service host is not running |
| STARTING | Yellow | Service host is starting up |
| STOPPING | Yellow | Service host is shutting down |
| RESTARTING | Deep sky blue | Service host is restarting |
During TUI startup the footer slot shows a live starting stopwatch with a rotating glyph in place of the host status, ticking at ~10 Hz until the TUI finishes mounting and the real host status resolves. The background color turns from its normal tone to a slow-startup tone once the elapsed time crosses the slow threshold, giving immediate visual feedback on cold-start latency. A safety timeout forcibly retires the stopwatch if the mount signal never fires.
Completed Statuses¶
The ordinary-row renderer reserves green for plain DONE; specialized terminal labels
fall back to dim text unless a gate or monitor supplies lifecycle presentation.
| Status | Presentation | Description |
|---|---|---|
| DONE | Green | Agent completed successfully |
| PLAN DONE | Dim | Plan workflow fully completed (all steps) |
| TALE DONE | Dim | Tale plan workflow fully completed (all follow-ups) |
| PLAN COMMITTED | Grey on a settled gate; otherwise dim | Commit-only plan was archived successfully |
| EPIC CREATED | Dim | A created epic ID is known, or a legacy epic follow-up completed |
| FAILED | Red | Agent exited with an error |
| PLAN FAILED | Red | Accepted plan action or archive failed |
| EPIC FAILED | Red | Accepted epic-creation action failed |
Monitor turns are the exception to this status table's success-oriented labels: a
gate-approved epic monitor uses EPIC APPROVED as its start label and EPIC CREATED as
its stop label for every terminal state. A failed, timed-out, stopped, or lost monitor
can therefore display EPIC CREATED in its state-dependent color. Inspect the monitor
state, bucket, exit code, and output; on the planner row itself, EPIC CREATED means
the created epic ID was successfully back-filled.
Completed agents can be dismissed with x on a single row, or through the X cleanup
panel for focused-panel, global, tribe, clan, marked, group, and custom planner-backed
selections. DONE, PLAN DONE, and TALE DONE rows with a saved response path are
resumable from the Agents tab.
When a terminal agent becomes unread, sase's TUI marks it with the completed-agent
indicator and includes it in the Agents header unread count. Selecting that row, jumping
to it with ,j, or toggling it back to read with U acknowledges the row and dismisses
the matching user-agent completion notification. Manually marking a row unread with U
arms it for normal acknowledgement after you move away and return, so the marker can be
used as a short-lived reminder without leaving stale inbox entries.
If the currently focused row finishes while you are already on the Agents tab, sase's TUI still marks it unread and keeps the completion notification active until a real navigation or selection event acknowledges it. A refresh that merely preserves focus does not silently consume the unread marker.
The unread count in the Agents header is drawn as black text on a gold pill so the
"you still have unseen completed work" signal stands out from the rest of the colored
metrics. It uses the same gold as the inbox's ✉ general-notification chip in the top
bar, giving you a single color to scan for.
Switching to the Agents tab does not bulk-dismiss completion notifications. sase's TUI
projects active completion notifications onto unread rows, then acknowledges rows one at
a time when you select or navigate into a terminal unread row. Bulk acknowledgement is
explicit through ,u. That command marks every loaded unread completed agent read: rows
on every Agents tab, members of collapsed clans and tribes, and rows that belong to a
query which is not the visible tab. Clan containers, session members, workflow children,
monitors, gates, and named procs stay out of the set, and the gold unread pill counts
the same rows, so the toast count matches the header. The toast reads
Marked N completed agents read · press ,u within 10s to undo. With no new unread
completions, press ,u again within those 10 seconds to restore the marked agents that
are still loaded and terminal (Restored N completed agents unread). A newly unread
completion cancels the undo; the next ,u marks the current unread set read instead.
When nothing is unread and no valid undo remains, ,u reports
No unread completed agents. This undo belongs to the current TUI session. Plan
approvals and user questions are never auto-dismissed by this flow; they always require
explicit y / n confirmation from their respective modals.
Agent Revival¶
Press !R on the Agents tab to revive previously dismissed work. sase's TUI opens the
saved-group revival modal first, showing newest saved groups with a right-hand preview
of included agents, projects, PRs, statuses, provider/model labels, and revival count.
Select a group and press Enter to revive it, choose Load more saved groups... to
page older groups, or choose Custom revival search... to open the older
dismissed-agent search where you choose all, home, project, or PR scope manually. In
that dismissed-agent search, Ctrl+J loads ace.page_size more archive rows
(default 100) and Ctrl+K unloads the last page, never dropping below the first page.
Use m to mark related Agents-tab rows and then s to save and dismiss them as a
group. The save modal accepts an optional human name. Leaving it blank keeps the
generated display title, such as "3 agents from @review" or "2 agents in auth_retry".
Saving a marked group hides the selected rows from the normal Agents tab without killing
running processes. When a marked top-level workflow row has child rows, sase's TUI also
includes the children in the saved group so revival can restore the original tree.
Dismissed agents are saved as individual bundle files under month shards in
~/.sase/dismissed_bundles/YYYYMM/ and can be restored later. Saved group metadata
lives under ~/.sase/dismissed_agent_groups/ and stores stable references to those
bundle files plus the optional group name, status counts, projects, PRs, model/provider
metadata, and tribes. There is no limit on the number of dismissed agents or saved
groups that can be stored.
Dismiss operations are O(1) per agent: each agent is saved to its own JSON file rather
than a monolithic store. Parent workflow rows use <raw_suffix>.json; workflow children
use <raw_suffix>__c<step_index>.json. sase's TUI keeps a SQLite summary index in the
dismissed-bundle directory so the revive modal and internal lookups can list dismissed
agents without opening every bundle. Use sase agent archive verify to check that
maintenance index, or sase agent archive rebuild-index to rebuild it from bundle
files. The index stores metadata such as status, name, project, model, provider,
workflow, and Patch metadata; it is not a full-text copy of agent chat contents.
Revival removes the agent identity from the dismissed set, restores enough artifact
files for sase's TUI to rediscover the agent, and preserves the dismissed bundle as
historical recovery data. Saved-group revival skips missing bundle references with a
warning and restores the remaining agents. Group metadata is not deleted after revival;
sase's TUI marks the group with revived_at and increments times_revived so the modal
can show previous use. The reload path forces a full-history scan and can hydrate the
just-revived row directly from the bundle, so agents still appear after revive even if
the persistent artifact index was empty or stale.
Every revival also writes structured events to ~/.sase/logs/events.jsonl (start,
per-agent success, per-agent failure). Read them back with sase revive-log — see
Agent revival audit log for the record schema and
CLI flags.
Legacy Dismissed-Name Prefix¶
Current dismiss and revive operations preserve stored agent names, per-agent tribes, and
top-level/workflow-child identity. Older dismissed bundles may still contain
YYmmdd.<base> names from the previous dismissal model, and sase's TUI keeps
compatibility helpers for reading those bundles. Bare %wait (no target) intentionally
skips legacy dismissal-prefixed candidates so it anchors on a live, visible agent.
Agents Deck Picker¶
On the Agents tab, p opens a small centered deck picker for the focused deck panel.
One more keypress picks the deck directly: m Main, f Files, t Tools, n FINAL.
Pressing the opener again (pp with the default binding) switches the focused panel to
the last deck that panel showed, or to the cycle-previous deck when that panel has no
history yet, and closes the picker. The return row names the destination and badges it
last deck or previous. Esc and q close without changing anything. j/k move
the highlight (wrapping across the return row and the four deck rows) and Enter picks
the highlighted row; any other printable key is swallowed so nothing leaks through to
the tab. History is one deck deep per panel, updated by every real deck change (picker
letters, palette jumps, Ctrl+N / Ctrl+P, and a capital letter that changes the other
panel), and restored with the deck layout. The picker heading names the focused panel
(deck panel single, top / bottom or left / right in a split, zoomed while
zoomed), each deck row shows its letter, glyph, count, and whether it is already showing
(or shown in the other split panel), and the palette offers the same jumps as
Show <Main|Files|Tools|FINAL> deck in focused panel plus
Show last deck in focused panel. The old detail-view picker is retired: the Agents tab
now always shows agent data decks and cards. The
in-picker deck letters are fixed and not configurable. p keeps its unrelated Artifacts
project-scope meaning on the Artifacts tab.
The capital deck letters (M / F / T / N) show that deck in the most recently
focused other panel instead of the focused one. When the opener is a single letter, its
capital (P by default) shows the same resolved return deck in the other panel, with
the existing split and zoom rules. From a single panel they open a new panel below
(top-bottom) showing the picked deck; in an existing top-bottom or left-right split they
target the MRU other panel and keep the layout as is (a left-right split is never
rotated). While zoomed, the zoom ends first, the way Z ends it, and the split comes
back in its original orientation (or a new bottom panel opens when the zoom came from a
single panel). Focus always stays on the panel the picker was opened from, unlike \,
which moves focus into the new panel; Ctrl+F moves it if you want. A muted hint line
at the bottom of the picker names the MRU target with its position glyph (for example
show in the ◲ bottom-right panel) and says where the capital letter will go. Picking a
capital letter for the deck the other panel already shows changes nothing. Enter and
mouse clicks keep their focused-panel meaning. The palette offers the same jumps as
Show <Main|Files|Tools|FINAL> deck in other panel plus
Show last deck in other panel.
Agent Data Decks and Cards¶
The Agents tab detail column shows one to three deck panels between the sticky header
panel and the jump panel. Each deck panel shows one agent data deck, a named ordered set
of agent data cards about the selected node. Ctrl+N / Ctrl+P cycle the focused panel
through the Main, Files, Tools, and FINAL decks (wrapping, nothing skipped), and p
opens the deck picker for a two-key jump to any deck (pp
returns to the last deck, or to the cycle-previous deck when the focused panel has no
history); Ctrl+J / Ctrl+K move to the next / previous card in the focused panel.
- Main deck.
Context(details and prompt; the default card) andReply— titledOutputfor named procs, monitors, gates, and workflow steps — plus a leadingTRACEBACKsection at the top of Reply when the agent failed. Clan rows and whole-panel tribe focus show a singleSummarycard instead. A paged deck keeps the card last chosen withCtrl+J/Ctrl+K(for example Reply) as the selection moves between nodes. - Files deck. One card per file page (commit diffs, the live diff, linked-repo diffs, then extra files), titled by file. The default card is the same default page the file view always chose.
- Tools deck. Two cards:
⚒ Runsfirst, thenLLM Calls(see Agents Tab Tool Runs and Agents Tab LLM Calls Panel). The Runs card exists only when the selected node has at least one tool run. The default card is the panel's sticky Tools preference when it names a card that exists, otherwise Runs, otherwise LLM Calls — so a reader who prefers LLM Calls stays there. Tools is paged-only: it never spreads,Pstays a no-op, and there is no deck-view badge. The border subtitle's Tools segment readstools ⚒N M(N runs, M LLM calls;tools ⚒Nwhen there are no calls), bold accent while a run is live and red while one is silent. - FINAL deck. The
⊛ FINALdeck shows how the selected node's turns landed: anOverviewcard plus one card per finalizer instance (see Finalizers on the Agents tab).
A multi-card deck renders spread — every card on one scrollable page separated by
titled rules — when its cards fit within ace.agent_decks.spread_max_screens panel
viewport heights (default 1.5; 0 means always paged), and paged — one card at a
time — otherwise. In spread mode Ctrl+J / Ctrl+K scroll the next / previous card's
header to the top. The deck panel's border title names the deck, its
deck view badge (Main and Files decks only), and its cards with the
active card highlighted (and an N/M position when there is more than one card); the
border subtitle is a main · files · tools · final switcher showing each deck's card,
file, run/call, or finalizer count when known, dimming decks with no content. A deck
with no content for the selection shows an empty-state card instead, so the layout never
jumps.
Deck Views¶
Each Main and Files deck panel has a deck view: spread (every card on one
scrollable page), page cards (one whole card per page), or page blocks (one card
block per page). The view is normally automatic — resolved from the content through
the ace.agent_decks spread thresholds — or fixed to one layout by you. The Tools
and FINAL decks have no views: they always lay out automatically and show no badge. Deck
views stay a Main-and-Files feature; FINAL's spread/paged and block paging resolve
automatically from the ace.agent_decks thresholds like every other automatic deck.
The badge. The top border title shows the effective view right after the deck name,
for example ◆ MAIN page blocks · auto or ◆ MAIN spread · fixed. The layout word
uses the deck accent; fixed is bold accent while auto stays muted, so a fixed panel
stands out at a glance. On narrow panels the badge shortens (cards, blocks, B·A)
but never depends on color alone. No badge appears for the Tools or FINAL deck, an empty
deck, or a panel that has never painted a full Main document.
P cycles the view. P steps the focused panel's Main or Files deck wider
(page blocks → page cards → spread, wrapping) and pins the result as a fixed view.
The first press from automatic starts after the effective layout, so it always changes
what is on screen; layouts that would render identically for the current content are
skipped. P is unavailable when there is nothing to choose (a one-card deck, the Tools
or FINAL deck, an empty deck, or while a Main document is still partial). The footer
shows a P view entry and the help modal an Agents › Navigation row only while the
cycle is available. The first press that fixes a view posts one teaching toast naming
the palette reset; routine presses stay silent because the badge changes in place. The
badge and panel chrome repaint on the first frame after P; the Main body for the new
layout is built off the event loop and swapped in when ready, so on a very long Reply
the badge can lead the body by a moment.
Palette. The command palette offers Cycle deck view plus four direct choices:
Deck view: automatic, Deck view: spread (fixed), Deck view: page cards (fixed),
and Deck view: page blocks (fixed) (Main only — Files never pages blocks). Only
automatic returns a panel to automatic; P cycles just the three fixed layouts. The
choice equal to the current policy is hidden. A direct choice applies to the focused
panel's deck even when it changes nothing for the current card, since it also covers
future cards.
Persistence. Each panel keeps one view per deck (Main and Files), surviving card and
deck switches, splits, zoom, and restarts in ~/.sase/ace_agents_deck_state.json. New
panels start automatic. A view change keeps the reader's place: the same card, block,
scroll offset, and pin survive in every direction.
Files specifics. A fixed spread on Files reads every page fully (capped per page,
with truncation hints, so nothing is silently dropped). While that read runs the deck
stays paged and the badge reads page cards · spreading…. Files with an image or video
can never spread: the deck stays paged with page cards · spread unavailable, keeps the
fixed preference for the next compatible selection, and toasts once when you caused the
change.
Card Blocks¶
Cards gain a third level — deck → card → card block — for the session Reply card. An
agent session's Reply card holds one block per concrete session turn (AGENT (role),
⚙ MONITOR, ⋔ GATE phases, matching the SESSION TURNS jump-panel roster); the
still-reachable legacy non-session followup_agents Reply path gets one block per
followup the same way. Every other card renders exactly as before.
Newest-block landing and the triage loop. Selecting a node lands on its newest
block, so the first thing you see is the latest turn's output. The working loop is: read
the newest block, press ( to step one turn older, repeat, and press ) to walk back
toward the newest. Block state is ephemeral and panel-local (kept per deck panel for the
current selection); changing nodes, toggling attempts, or losing a vanished block id
re-lands on the newest. While you sit on an older block and a new turn starts, the view
stays put and the newcomer only gains an arrival dot — a following view (sitting on the
newest block) advances to it automatically.
Block-spread vs block-paged. Only a card shown alone pages its blocks; a spread deck
always shows every block inline. A lone card renders block-spread (all blocks on one
page) when it fits within ace.agent_decks.block_spread_max_screens panel viewport
heights (default 1.5; 0 means always one block per page), and block-paged (one
block per page) otherwise. Block navigation never flips the deck's own spread/paged
mode.
The block rail. Whenever a paged deck's active card has 2+ blocks, a one-row rail
sits docked under the Main deck panel's top border showing the session timeline:
roster-numbered entries with status colors, an accent pill on the active block, arrival
dots on unseen newcomers, and a ( ) blocks key hint at wide widths. At its widest tier
the rail names the block mode just before the key hint: page N/M when blocks are
paged, all N when they are inline. Clicking an entry selects that block; in a spread
deck the rail stays hidden because the phase dividers already mark each turn.
Keys. ( steps to the older block and ) to the newer block (both wrap); in a
spread deck they top-align the target turn's header, in a block-paged card they swap the
page. The footer shows a (/) blocks entry and the help modal a matching
Older / newer card block row only while the focused card has 2+ navigable blocks.
Ctrl+Shift+J / Ctrl+Shift+K are not bound by default: in common terminal chains
(tmux + kitty included) they arrive as plain Ctrl+J / Ctrl+K and would cycle cards,
not blocks. If your terminal delivers them distinctly, bind them yourself as a personal
override of prev_card_block / next_card_block (see
configuration and the keymap settings).
\ opens a second panel below the first (stacked) and | opens one to its right (side
by side); the new panel takes focus and shows the next deck with content, or a duplicate
deck when every other deck is empty or already shown. One rule governs the split keys:
\ draws a stacked divider and | a side-by-side divider. If that kind of divider
already spans the whole area, the key erases it and the side you are on grows to fill
the space. Otherwise, with fewer than three panels, the key draws the divider through
the focused panel — the unfocused panel becomes the full-span main panel without moving
or resizing. With three panels, the key turns the layout instead. There are seven
geometries: single, two two-pane splits, and four three-pane T shapes with a full-span
main panel:
single R2 stacked C2 side by side
┌──────┐ ┌──────┐ ┌───┬───┐
│ A │ │ A │ │ A │ B │
│ │ ├──────┤ │ │ │
└──────┘ │ B │ └───┴───┘
└──────┘
R3 main-top R3 main-bottom C3 main-left C3 main-right
┌──────┐ ┌───┬───┐ ┌───┬───┐ ┌───┬───┐
│ A │ │ B │ C │ │ │ B │ │ B │ │
├───┬──┤ ├───┴───┤ │ A ├───┤ ├───┤ A │
│ B │C │ │ A │ │ │ C │ │ C │ │
└───┴──┘ └───────┘ └───┴───┘ └───┴───┘
Ctrl+T turns any split, Ctrl+Shift+D (alias Ctrl+X) closes the focused panel, and
Ctrl+Shift+F / Ctrl+Shift+B (aliases > / <) swap the focused panel's session
with the next / previous panel in reading order. p plus a capital deck letter (M /
F / T / N) opens or fills the most recently focused other panel with a chosen deck
without moving focus (see the deck picker). Ctrl+F / Ctrl+B
move focus to the next / previous panel in reading order (split layouts only) and every
deck, card, scroll, search, and fold key acts on the focused panel. } / { grow /
shrink the focused panel (split layouts only). A new three-pane geometry needs about 8
rows by 40 columns per panel or the key is refused with a toast; shrinking the terminal
never closes a panel.
The Ctrl+Shift chords need the kitty → tmux CSI-u chain (kitty plus tmux with
extended-keys-format csi-u, and SASE requesting modifyOtherKeys mode 2 inside tmux);
in mode 2, tmux re-encodes pasted control characters (newlines arrive as CSI 106;5u),
and SASE's input driver decodes them inside bracketed pastes so pasted text arrives
intact. > / < / Ctrl+X always work.
The layout, split ratio, focus, node-rail preference, and each panel's deck, preferred
card, and deck views persist across restarts in ~/.sase/ace_agents_deck_state.json.
See the key tables for the full deck keymap and
configuration for the spread setting.
Finalizers on the Agents Tab¶
Host-owned finalizers (commit, check, tasks, plugin finalizers) run after the model
turn, and the Agents tab shows them at four zoom levels over one shared state
vocabulary: at a glance on the row, in context in Reply, to diagnose in the ⊛ FINAL
deck, and for authorship in the Overview card and sase final status. Every surface
agrees because glyph, word, and color for each state live in one mapping. The view is
read-only: there are no retry, cancel, or bypass controls.
At a glance: FINALIZING rows and ⊛ chips. While a node's finalizers run, its row
status word reads FINALIZING in the Running bucket. This is a presentation overlay
only: agent.status, buckets, ordering, filters, capacity, and row actions are
untouched. A ⊛ chip names the most severe finalizer state. On a session container the
chip considers only runs after the newest successful settled run, picks the highest
severity (failed > refused > interrupted > deferred > running), and appends
k of n runs when more than one run is in play. The collapsed header panel carries the
same signal as an activity chip (⊛ finalizing · <id> · <label>, or ⊛ declaration
while the declaration turn runs). A clan whose lone running member is finalizing also
reads FINALIZING, while the ⊛ chip stays on the member row.
In context: the ⊛ FINAL receipt. Each turn's Reply phase ends with a short ⊛ FINAL
receipt: one line per finalizer instance with its state glyph, per-instance detail (step
or op label, attempt n/m when retries are budgeted, failure reason or headline), and
duration. Success lines stay quiet; success with warnings gains a dim ⚠N suffix
(detail lives in FINAL, never on the row), and failed or refused instances add a dim
reason line. When anything needs attention the receipt ends with a
p n open FINAL deck hint. There is no receipt before finalization begins (the
planned phase), none for turns skipped by a plan handoff, and none for legacy runs
with no finalizer summary.
To diagnose: the ⊛ FINAL deck. Press p n (p N opens it in the other panel) or
cycle with Ctrl+N / Ctrl+P. The deck accent is rose #FF87D7; the count noun is
finalizer/finalizers. Cards are Overview plus one per selected finalizer instance,
and FINAL cards stick while j/k move (the status strip and subtitle carry the signal
instead). Every FINAL card on a session container holds one card block per turn that ran
finalizers, with the same roster-matched ids Reply uses; skipped and not-triggered turns
appear only in the ledger. The rail, ( / ), and newest-block landing all work as on
Reply.
- Overview card. The run plan in DAG order with per-instance selection reasons,
configured-but-unselected instances dimmed with their reason (
%final:!lint,%final:none, not default), the declaration timeline, controller cycles (only when above one), drift, run-level diagnostics, the runs ledger, and pointers tosase final status. - Instance cards. Why, trigger, and declared lines; one section per finalizer
attempt with the latest (or failing) attempt expanded; operations with outcomes;
structured steps; typed evidence (
sha,url,bead); deduped diagnostics; and log and protocol hint targets for export and search. - States. Planned
◌, declaration/running▶(gold), success✓(green, plus a dim amber⚠Nin deck and receipt only), failed✗(red), refused⊘(purple), deferred⏸(amber), not triggered / handoff-skipped○(dim), blocked / not reached–(dim), interrupted!(amber), unavailable⚠(dim amber). An interrupted or unavailable state never spins. - Live tails. A running op shows a sanitized in-card tail of at most 12 lines, but
only once the op has run longer than
ace.agent_decks.final_tail_delay_seconds(default5.0;0renders immediately). The tail follows the newest run and latest attempt with an arrival marker, pauses while scrolled up, and refreshes on a 1 Hz tick that runs only while FINAL shows the selected agent's active finalization. Fast ops go straight from▶to✓with no tail.
For authors: sase final status. sase final status [<agent>] projects the same
run view the FINAL deck renders — reconciling the sealed plan, progress journal,
operation records, steps, and typed evidence — and prints it pretty (colored, default)
or as JSON (-f json). With -d/--artifacts-dir it reads finalizer artifacts directly.
Inside a SASE turn it defaults to the calling agent. See the
CLI docs.
Agents Tab Main Deck¶
The Agents tab Main deck shows structured information about the selected agent as
Context and Reply cards (see
Agent data decks and cards):
Pressing V on any local Agents-tab row (running or done) opens that same agent's
metadata full-screen in the pager instead, as a sectioned document —
IDENTITY, MODEL, WORKSPACE, TIMELINE, CONTENT, SASE CONTEXT, and BEAD (skipping any that
would be empty) — with / search, ; goto, Ctrl+N/Ctrl+P section jumps, and y
copy. r inside the pager re-snapshots the agent, which matters for a still-running
one. The pager also supports split panes (\ below, | beside) — see
Split panes. V is unavailable for remote fleet rows and when
no agent is selected; other tabs keep V bound to the Agent Run Log modal.
Ctrl+J and Ctrl+K move to the next / previous card in the focused deck panel
(wrapping). In a paged Main deck this swaps the visible card; in a spread Main deck it
scrolls the next / previous card's header to the top. The chosen card is the panel's
preferred card: on a new selection the panel shows the preferred card when the node has
it, otherwise the default card (Context, or Summary for clan and tribe documents),
while the preference itself is kept. Numbered roster rows (SESSION TURNS,
CLAN MEMBERS, TRIBE MEMBERS, NEIGHBORS) live in the jump panel and are not cards;
within a session container's SASE CONTEXT region, its lane sub-headings (BEAD,
PLAN, ARTIFACTS, MEMORY, GLOSSARY, SKILLS, WORKSPACES) are fold anchors, not
cards — za/zA still reach them when they own the viewport's top row. Roster rows are
also not za/zA targets and are not covered by ,/ search. The shortcuts act on the
focused deck panel, and changing agents or entering/leaving a pinned attempt view keeps
the preferred card.
- Agent details: Name, status, model, provider, Patch association, and chronologically sorted timestamps:
Bead— shown for agents launched bysase bead work; modern phase and task rows use explicit bead launch metadata, phase rows also use epic/plan metadata and validated plan frontmatter, and exact epic plus legacy phase/.landrows retain compatibility inferenceWAIT— when the agent was spawned (waiting for a slot)BEGIN— when runner admission completed, before workspace preparation for slot-participating user agentsPLAN— each plan proposal round (multiple entries when re-planning occurs)FBACK— each time the agent requested feedback from the userQUEST— each time the agent asked the user a questionRETRY— each time the agent entered retry state (retryable error)CODE— when the agent began writing codeEPIC— when an epic follow-up agent was launched after plan approvalDONE— when execution completed- CLAN / MEMBERS: Shown when a synthetic clan row is selected. The orchid
CLANkind label renders as the header panel title and the identity fields (Name,Tribes,Status,Runtime,Members,Fold) live in the header panel; theCLAN MEMBERSroster lives in the jump panel. Direct member rows use chronological launch order (earliest first), which keeps their numbers stable while statuses change. Each numbered row shows the hood-relative suffix, kind, status, model, and duration; members of a nested sequential session are indented under its aggregate row.Ctrl+J/Ctrl+Kmove between the Main deck's cards, and pressing the row's number jumps to that member in the Agents list. At most 100 members receive numbers. - SESSION: Shown when a real multi-member session root is selected. The cyan kind
label renders as the header panel title and the cyan
Name:value matches the session row's identity block. The title is header chrome, not a card; theSESSION TURNSroster lives in the jump panel and is not a navigable section. On a session container, itsSASE CONTEXTheading opens the Context card region; its per-lane sub-headings (BEAD,PLAN,ARTIFACTS,MEMORY,GLOSSARY,SKILLS,WORKSPACES) stay fold anchors only. - AGENT TURN: Shown when a standalone sase agent or session member row is selected.
The gold kind label renders as the header panel title and the gold
Name:value matches the list-row name annotation. The title is header chrome, not a card. Monitor members and workflow step children (bash/python/parallel) do not get this heading. - Header panel: The selected node's identity header (every field from the kind line
through Timestamps, plus Fold where present) renders in its own always-visible panel
at the top of the detail column, above the deck panels in every deck layout.
Collapsed, it keeps the two chip rows — who and how on row 1, what and state on row 2
— and previews the agent's
AGENT MACRObelow them as a card: anMACROtab row over a rectangle on the Monokai code surface, so the prompt reads as one block apart from the metadata chips. Every card row carries the▎quote bar in the MACRO accent, which acts as the card's accent edge; hard-wrapped prose reflows into wrapped rows, hard breaks render as a dim¶, and the highlighting matches the expanded prompt. The card fills the panel width and shows at most three rows (ace.agent_header.collapsed_preview_max_rows), fewer when theace.agent_header.collapsed_max_shareheight budget of a short column is tighter (0still turns the preview off and shows no tab). The tab row counts against that budget, next to the border and the chip rows. On overflow the last row ends in…and the border subtitle becomes+N lines · ▾ d more(+1 linewhen one line is hidden).dexpands the panel to the full field list plus the completeAGENT MACROunder its own heading (or collapses it back). The kind label moves into the panel's border title in the node's accent color —AGENT,AGENT TURN,SESSION,CLAN,WORKFLOW,STEP,GATE TURN,MONITOR TURN,NAMED PROC, or, for a selected whole tribe panel,TRIBE— and the border subtitle shows whatdwill do (▾ d more/▴ d less, naming the configuredtoggle_agent_headerkey). The panel is hidden only for "No agent selected".AGENT MACROno longer renders in the scrolling body and is not aCtrl+J/Ctrl+Kstop;,/still finds the user's words throughAGENT PROMPT. A clan's collapsed rows mirror the tribe layout: name, status, and count chip on row 1; tribes, member totals, runtime, and the fold chip on row 2. Collapsed/expanded state is per session and holds across row moves, tribe focus, and layout changes. Hint mode never changes the panel's state: expanded, its fields andAGENT MACROcarry hint markers numbered first; collapsed, it keeps its normal preview, header content gets no markers, and numbering starts in the deck body. Attempt-pinned views never renderedAGENT MACROand show no preview, and nodes without a macro show exactly the two chip rows inside the border. Deck search (,/) covers the focused panel's deck only, since header fields stay on screen. - Jump panel: Every live numbered roster target (session turns, neighbors, clan
members, tribe members) lives in its own always-visible panel at the bottom of the
detail column, below the deck panels in every deck layout; the Main deck body does not
contain these sections. The panel is shown only while the current document has
numbered targets — never for "No agent selected", nodes without rosters, fully
unnumbered rosters, or file-hint documents — and it stays visible during deck search
(
,/), since the digits keep working there. It is collapsed by default to at most two packed rows, where each visible number carries its roster label, shortened with a middle ellipsis only as far as it stays distinct from every other label (the packer shows fewer targets rather than ambiguous ones). When targets do not fit, the last cell is a dim+Ncount (with a lit▶MwhenMin-flight targets stay hidden); digits still reach every numbered target, including those counted in+N. In-flight targets fill the visible slots first, still in number order, and render lit in every view (see above)..expands the panel to the full roster sections, with their fold-driven annotations; expanded, it grows to at most 40% of the detail column, scrolls, and returns to its top when you select a different row. The border title is the color legend (JUMP, then one entry per section with its number range), and the border subtitle names the configuredtoggle_agent_jump_panelkey (▴ . more/▾ . less). Each collapsed cell echoes its roster row: the number chip in the roster style, the label, and the status glyph; a dismissed neighbor renders dim with a⊘prefix (and arevivenote when narrowed), since its digit revives the agent instead of jumping. After the first digit of a two-key jump, the panel narrows to the matching candidates (JUMP · 1▁,esc cancel), or showsno targets start with <digit>; completing or cancelling the jump restores the collapsed or expanded view. Collapsed/expanded state is per session and holds across row moves, tribe focus, and layout changes. Toggling never rebuilds the document, and a bottom-pinned body stays pinned. Roster headings are not cards;za/zAcannot target a roster section or roster row (rosters follow the global panel fold keyszz,zZ, and the direct level keys); and,/deck search does not cover roster rows. - SASE CONTEXT / BEAD: Shown for epic phase workers and task workers. For an epic
phase worker, the lane is limited to its selected phase. Its fields are
Phase Title,Description,Size,Epic Plan, andEpic Title, in that order. The phase title comes from the same validated, frontmatter-ordered phase entry, is normalized to one line, wraps losslessly, and renders a quietunavailablefor missing, unreadable, damaged, or out-of-range entries. Exact validated sizes use literal bluesmall, goldmedium, or roselargechips; missing/unreadable/damaged plans, explicit invalid sizes, and out-of-range phase ordinals also show a quietunavailablesize. Modern explicit phase metadata avoids bead-store reads. The parent goal, dependencies, and peer phases are never rendered, and the parent plan does not become a generic artifact. For a task worker, the fields areTask Title,Description, optionalNotes,Size, optional+1 Reports/+1 Evidence, andCreated. A multi-lineNotesvalue (both bead types) or+1 Evidencevalue (task only) collapses to a one-lineN lines (zz to show)digest at metadata fold level 1 and renders in full at levels 2-3; single-line values never fold. - SASE CONTEXT / PLAN: Shown for the epic-authoring planner, epic lander, and task
workers with a distinct authored plan when direct metadata or a confirmed legacy epic
association resolves a plan. Phase workers deliberately omit the parent epic lane; no
goal or peer roadmap phase is rendered. A task bead's own
designfield is never rendered as the task worker'sPLANlane. For plan-bearing roles, the body rows areTitle,Goal, and canonicalPath, in that order; a tale additionally gets aSizerow betweenGoalandPath(the authoredxsmall/small/mediumchip, or a defaultedmediumchip when the tale'ssizewas missing or an over-sized legacylarge/xlargenormalized at launch). The lane header carries the effective tier (plan,tale, orepic) and an epic's phase count. Anapproveaction displaysplan,taleand legacy commit-only actions displaytale, and anepicaction displaysepic, even when the corresponding commit or launch later fails. Without action metadata, a valid authored tale or epic supplies the tier; legacy committed plans without a readable authored tier displaytale, and unresolved values displaytier unavailable. Canonical path selection remains separate: committed paths are workspace-relative, while pending or explicitly uncommitted paths use the home-shortened machine-local archive. Valid authored epics then show every phase in authored order with its title, fixed-width literal size chip, ID, dependency IDs, optional model, and optional description; these are static roadmap ordinals, not progress indicators. Launch-consumption validation normalizes only an omitted historical size tosmall; explicit invalid sizes remain unavailable. The chip stays visible while the title and every other value wrap without truncation in the normal panel and zoomed deck view, and logical text exposes the same labels to deck search and copy. Only the path participates in file hint mode. Invalid known epics showphases unavailablein the lane header without leaking partial entries; tales do not show a phase roadmap. A plan alone rendersSASE CONTEXT; across every combination of present lanes, the full order isPLAN,BEAD,ARTIFACTS,MEMORY,GLOSSARY,SKILLS, thenWORKSPACES, with absent lanes omitted once they resolve and still-resolving lanes holding their slot with a dimresolving…row. - SASE CONTEXT / GLOSSARY: Shown directly after
MEMORYwhenever the selected agent or session has at least one audited event under the retired, pre-websase glossary readcommand's legacy log. Currentsase memory read glossary:<keyword>reads are not legacy events, so they surface in theMEMORYlane like any other memory read; this lane is purely historical (see Memory Webs). That lane's hints open the requested memory files (see Audited Reads). The lane header counts reads and distinct requested terms, adding the agent count for a multi-agent session. Each row shows the read's requested terms (truncated the same wayMEMORYtruncates paths), with a+N relatedsuffix when the closure expanded past the requested terms, and the recorded reason on its own indented line. A numbered hint pages a generated report of that read's output — the reproduced command line, recorded metadata, and the resolved term closure.@opens the report and%copies its path; the report names the recordedsase/sase.ymlsource, since that legacy format predates the strand migration. Loading, attribution, and the mtime/size snapshot cache mirrorMEMORY's reference implementation; the lane is skipped rather than rendered empty when there are no reads to show. - SASE CONTEXT / ARTIFACTS: The plan-adjacent lane groups
Beads,Reads,Commits,Deltas, andFilesas compact fields, preserves that internal order, and summarizes only the present fields in its header.Beadscomes first and lists every bead the agent interacted with, newest first: each row shows the local time, one verb glyph, the full bead ID, an amber▐CREATED▌pill for beads the agent created (a standing close keeps its green/grey▐CLOSED▌pill priority and adds a compactcreatedchip instead), and verb chips (assignedfirst for assignment-only rows, then durable verbs such asnotedorclosed, thenread, thenviewed; repeats render×N). Indented↳lines show the bead title when available, then a labeledwhy:filing-reason line for created rows (the bead's creation reason), then the standing-close reason and the newest audited read reason with explicitclosed:/read:labels; rows with none of these omit↳lines.assignedmarks the agent's assigned phase, epic, orsase bead workbead even when it was never touched (such rows show the already-resolved title when available, have no timestamp, and sort last); it never claims the agent created the bead. At most the newest five rows render; when there are more, a dim+ N more · HH:MM earliestfooter reports the rest. A numbered hint opens the bead's live detail (thesase bead showview) in sase's pager,%copies the bead ID, and@is not supported for bead rows. Session rows add the producer label when every contributor to that bead shares one producer, and clan rows include their members' beads (de-duplicated and labeled per member). Auditedbead:reads live here and are excluded fromReads. The data and glyphs matchsase bead touched.Readsis the input side of the lane: each retained auditedsase artifact read(including when artifact links are disabled) appears newest-first with local time, the canonical reference, the recorded reason on a wrapped continuation line, and — on a session row — the compact producer label. The header counts every retained read event; the newest five rows render and a dim+ N more · HH:MM earliestfooter reports overflow. Repeated reads of the same reference stay separate. Prompt citations and silentshow/path/opencommands never appear. A read with a recorded resolved path participates in hint mode; a pathless or legacy row still renders its reference and reason, consumes no hint number, and never triggers live reference resolution. Commits persisted by the selected agent's post-run steps are grouped by repository; primary workspace, linked-repo, sidecar, and external-repo commits retain their repository identity. Deltas preserve their green+, gold~, and red-change glyphs and group linked or external files by repository. Artifact type remains visible through its icon shape, while every artifact icon, read row, and path uses the shared blue output-lane/file-path palette. This lane starts painting on the very first navigation frame:Commitsis derived from the selected agent's in-memory step metadata and needs no disk reads, so it renders immediately, whileReads,Deltas, andFiles— which do need store reads — fill in when the debounced enrichment resolves the lane. The immediate commit-only view is deliberately not cached, so the full lane still resolves on its normal schedule. An agent whose only artifact context is a read still getsSASE CONTEXTand anARTIFACTSlane. - Slow tool calls: The metadata header lists tool calls that took 20 seconds or
longer, ordered by start time and capped at 8 rows (an overflow line points to the
full LLM Calls panel timeline via
pthent). Level 1 is a compact triage table: every row keeps its timestamp, state, tool, duration, and a short path-, query-, or command-aware digest, while a dim tail reports that full commands are hidden. From position 2 upward, each row adds the complete command or target in an indented block that wraps with a hanging indent, plus start/end and outcome facts and any error. The lane's last position also adds output previews, subagent tool/token statistics, and each call's rank and share of selected slow time. These tiers are positional: an ordinary agent uses compact/detail/full across its three levels, while a session uses compact/full across its two.zaandzAcan change only this section. For a root agent the list aggregates calls across its children while attributing each call to the child that made it. - Wait state: For a
WAITINGagent gated by%wait, a duration wait, or an absolute-time wait, the detail view shows a taggedWait:block with one lane per active dimension:[agents],[beads],[time], then[capacity]. Present tags occupy a padded gutter, so every value begins in one aligned column and long dependency lists wrap with a hanging indent beneath that value column. The[agents]lane lists the dependency names recorded on the waiting agent, adds per-name status badges for currently known agents, clan containers, or session containers, and marks unknown names with?so typos and stale references are obvious. When a waited-on target is followed into an epic it launched, the[agents]lane narrates the hand-off in place with a teal↪:planner ✓ ↪ sase-7k ◐ in progress · 2/5 phases · since 14:32while following,↪ epic launching…while the launch is in flight, or↪ ! epic launch ended without an epic · resume: …when blocked. A canceled or superseded followed epic shows its resolution in place of the status word. Followed epics are never repeated in[beads]; that lane lists only authored bead waits. The[beads]lane uses the same status-bearing token as the compact row, without a count:run-bead ◐,done-bead ●, andbead-id ?for an unknown bead. A WAITING list row keeps agent and bead counts independent while placing matching bead statuses after their present agent status (▶1 ◐2,✓1 ●1); unmatched bead tokens trail in bead order. Unknown targets can render as adjacent independent counts (?1 ?2). Timed-only and capacity-only waits do not receive those markers. Timed waits add compact duration, target time, and countdown text when available. An authored capacity on aQUEUEDrow shows as the samecNbadge used elsewhere, and its detail context reports occupied capacity against that row's own admission budget. AQUEUEDdetail uses a separateQueue:line led by its rank and elapsed time sinceslot_requested_at, followed by cap context. It deliberately suppresses the marker's stale dependency, bead, and time-wait fields. - OUTPUT VARIABLES: Small JSON-shaped values written by the selected agent session
with
sase var setand bysase artifact create(the SASE-managedartifactslist). Strings, numbers, booleans, null, lists, and nested maps retain their types. A single contributing agent renders as a flat sorted key/value block; multiple session members render with compact role labels so root, planner, coder, tester, and follow-up values stay attributable. Lists, maps, and multi-line strings use an indented YAML-shaped block with type-specific colors. The section is omitted when the session has not published variables. These values are stored inagent_meta.json, so they are visible metadata rather than secret storage. - TRACEBACK: When an agent or workflow step recorded an error traceback, it renders
under its own
TRACEBACKheading after the prompt, directly above the reply heading (AGENT REPLYwhile running,AGENT CHATonce done or failed, orSTEP OUTPUTfor a workflow step), and is aCtrl+J/Ctrl+Kstop. - AGENT REPLY: The agent's live or completed reply content, streamed from
live_reply.mdduring execution and read from the artifacts directory after completion. When per-turn reply timestamps are available (recorded inlive_reply_timestamps.jsonl), the reply is displayed with timestamp dividers between each agent turn. While the Agents tab stays on that row, one in-flight reply follows those two files in place. The Reply card replaces only that live reply body; the rest of the detail document stays put, and a change to those two files does not refresh the agent list. The followed row is a selected running agent that is still executing. On a sequential session, that is only the current in-flight turn, and only when that turn itself qualifies; otherwise nothing in that session is followed. A row that is not a sequential session follows the row itself, or the latest of its follow-up agents, when that candidate qualifies. Bash and Python workflow steps are never followed. The first paint of an empty solo reply saysWaiting for agent response.... An empty session phase first saysNo response content yet.. The follow leaves that first-paint text in place until a timestamp chunk or reply text exists. If a reply that was already followed is cleared, the body becomesWaiting for agent response.... Hint mode keeps the first-paint text because the follow stays off. A change to either file schedules an update, and a status check of both files about once a second is the backstop. While the follow is idle, later updates are at least 0.3 seconds apart; the first update can be immediate. The follow waits while you are navigating or the prompt bar is open, and retries every 0.25 seconds. It stops for this row when you leave the Agents tab, when the detail view is pinned to one prior attempt number, or while hint mode is showing. Changing the selection cancels this follow; the newly selected row starts its own when it qualifies.Dswitches between the merged history and the current attempt only. That switch ends the current follow, and the view you land on starts its own when it still qualifies and no attempt number is pinned. If either file changes while it is being read, that snapshot is discarded and the follow tries again. For agents with follow-up phases (planner, feedback rounds, coder), the AGENT REPLY section consolidates replies from all phases into a single view with phase dividers showing each phase's label and start time. Phases follow the session's chain order: a monitor phase renders immediately after the turn that started it, including a monitor started by the session root, which renders after the root's own phase. Agent-turn members follow one rule,AGENT (<role>), derived from the member's session role:--planrenders asAGENT (plan),--codeasAGENT (code),--epicasAGENT (epic),--commitasAGENT (commit), and numeric feedback suffixes such as--2asAGENT (plan round 2). Custom session members render the same way with their suffix token, e.g.AGENT (bar). A monitor member is a named proc, so its phase renders as an amber⚙ MONITORdivider followed by the monitor's command, its recorded detail fields, and its full captured output — the same block the monitor's own panel shows. A gate-turn member renders as a lifecycle-colored⋔ GATEdivider with its decision, kind, state, deadline, reason, request identity, branch policy, follow-up disposition, and captured command output. Its phase remains in the consolidated session reply after settlement, including terminal branches that intentionally launch no successor. Legacy dotted and single-dash suffixes render the same way. - WORKFLOW VARIABLES: macro workflow output variables from step outputs with
additional
meta_*keys are grouped under a dedicated header. The special routing keysmeta_project,meta_patch, andmeta_workspaceare promoted into the normal header fields;meta_changespecremains accepted as a legacy alias formeta_patch. Other metadata keys are title-cased and shown in this section. - PROMPT: For agents launched from a multi-agent (
----separated) prompt, the final, planner, and question transcripts include aPROMPT:row linking the saved original launch prompt (stored under~/.sase/.../multi_prompts/), so the exact text that fanned out into every segment stays recoverable.
g / G, Ctrl+D / Ctrl+U, and the bottom pin act on the focused deck panel.
Agents Tab LLM Calls Panel¶
The Tools deck's LLM Calls card shows a chronological timeline of the LLM tool calls
the selected agent has made — file reads, edits, bash invocations, web fetches,
sub-agent launches, and so on.
Entries are read from the tool_calls.jsonl artifact in the agent's run directory. Each
call renders as one timeline row:
- A status label colored by outcome —
ok(success),fail(error),stop(interrupted),agent(sub-agent launch), orwait(the post-call record has not arrived yet). - The tool name, optionally followed by a compact target (such as the file path the tool acted on) and the call's duration.
- A short preview of the call result on the next line, when the collector captured one. Command-output previews keep a marked suffix with at least the final 50 logical lines; the character budget is soft so unusually wide trailing lines remain complete. Other preview types remain head-oriented.
The panel header shows the total call count, the failure count, the interrupted count,
and a timestamp for the most recent reload. While a background reload is in flight
(because the artifact changed on disk), (refreshing...) appears next to that
timestamp. The body shows No provider tool-call artifact available when the file does
not yet exist for this agent and No tool calls recorded when the file exists but
contains zero records.
The timeline has three detail levels: compact, expanded, and full. While the focused
deck panel shows the Tools deck, l / h step the level up / down and L / H jump
to full / compact, taking priority over their usual fold actions.
For retry chains and planner-to-coder follow-up sessions, the panel aggregates
tool_calls.jsonl from related artifact directories so the selected logical agent shows
one ordered tool timeline. Discovery uses the persistent artifact index when it is
available; if the index is missing or stale, sase's TUI falls back to direct lineage
pointers plus a bounded scan of nearby legacy sibling artifacts.
Records are produced by writers that share one normalized on-disk format. Claude uses
the SASE tool-call hook collector as the preferred source and keeps its stream-derived
parser as a fallback when hooks are unavailable. Codex writes equivalent rows from its
codex exec --json stream with runtime: "codex" and source: "stream"; current Codex
start/completion events can show pending rows, result previews, failures, interruptions,
and durations, while older completed-only function_call rows remain readable with more
limited detail. Qwen writes stream-derived rows from its --output-format stream-json
output with runtime: "qwen" and source: "stream"; start/completion (and Qwen's
tool_use / tool_result) pairs collapse into single rows the same way Codex pairs do.
Muse Code writes stream-derived rows from its muse exec --json JSONL stream with
runtime: "muse" and source: "stream"; proposed/scheduled and tool.result pairs
collapse into single rows like the others. Muse's stream never carries tool arguments,
so a Muse row's input target comes from edit_facts.path for edits, the parsed command
for bash, and otherwise a bounded preview of the result text — SASE does not invent
arguments Muse did not emit. Antigravity (agy) runs in plain-stdout mode; SASE never
scrapes display prose, but supported Antigravity versions may contribute guarded
source: "trajectory" rows from the local trajectory DB. When that extractor is
unavailable, the panel simply shows nothing for agy runs. Grok Build writes
stream-derived rows from its streaming-messages-json output with runtime: "grok" and
source: "stream"; Grok's native tool names (run_terminal_command, read_file,
search_replace, and so on) are mapped onto the same canonical display names Claude
rows use, and its JSON-encoded tool_result envelopes are decoded for exit codes and
file paths rather than shown raw. See
LLM Providers — Claude tool calls,
LLM Providers — Codex tool-call capture,
LLM Providers — Qwen tool-call capture,
LLM Providers — Muse tool-call capture,
LLM Providers — Antigravity (agy) Integration,
and LLM Providers — Grok Tool-Call Capture for
provider integration details.
Agents Tab Tool Runs¶
The Agents tab surfaces the machine-local ToolRun ledger (see
Named Tools and ToolRuns) at three zoom levels: a live-only ⚒ chip on the
owning row, a header chip plus Tool runs field on the selection, and the Tools deck's
⚒ Runs card for diagnosis. Project-wide runs live in the Admin Center
Tools tab. ⚒ (U+2692) means ToolRun everywhere — row, header, card,
pane, notification icon, and Procs cell. The old chop/Services link-trail icon moved to
⏲ so the two are never confused. Say "tool run" or "ToolRun": never "tool call" for a
ToolRun.
The view is read-only. The TUI never reconciles, settles, or writes to the ledger on any UI path: stopping a run goes through the explicit stop flow below, and everything else only reads.
State vocabulary¶
Every state shows glyph + word + color together, so meaning never depends on color alone:
| State | Glyph | Words (examples) | Color |
|---|---|---|---|
| running | ⚒ |
check 7/11, check 2m, check starting, check stopping |
bold Tools accent #87D7FF |
| silent | ⚒⚠ |
check silent 4m |
bold #FF5F5F |
| pass | ✓ |
pass |
#5FD75F |
| new failures | ✗ |
3 NEW · 1 KNOWN |
#FF5F5F |
| known only | ≈ |
known only · 2 KNOWN |
#87AF87 (calm: the known red, not yours) |
| undetermined | ? |
2 UNKNOWN, or untriaged |
#FFAF5F |
| killed | ⊘ |
killed at 9m00s · signal, timed out at 30m00s |
#D75FFF |
| stopped | ⊘ |
stopped at 1m02s |
dim |
| lost | ⊘ |
lost · wrapper_lost |
dim #FF5F5F |
A live run is one in state created or running. A live run is silent when nothing
was heard from it for 60 seconds (six missed samples): the TUI says silent in red and
never claims an outcome. Severity picks one chip among several tools —
new_failures > undetermined > killed > lost > stopped > known_only > pass — with live
always outranking settled and silent outranking live. Ages format as <1m, Nm, Nh,
Nd.
At a glance: the row chip¶
A row carries a ⚒ chip only while one of its runs is live; settled history never marks
the row. The chip shows the tool label plus progress against the reference run
(check 7/11), elapsed minutes (check 2m), starting for a created run, or
stopping once a stop is requested. All times are minute-resolution snapshots — a
relative time that doesn't tick would be a lie, and rows don't repaint on a tick.
Attribution is by node kind, never by name guessing. A monitor-owned run chips the
monitor row, not its starter; a named-proc run chips the proc row. History still shows
both relationships (a handed-off run is labeled → monitor <name> or → proc <id>). A
session container inherits its members' most severe live chip. Rows on remote machines
get no chip: the ledger is machine-local, so absence must never read as "no runs".
In context: the header chip and Tool runs field¶
The selected node's header chip answers "did its latest check add NEW failures?" at a
glance: a live run wins over settled history (silent first), otherwise the most severe
settled run per label wins, with +N when other tool labels exist. Live elapsed turns
amber past the run's typical duration; settled times are absolute (16:21), never
relative. The expanded Tool runs field lists each run with its verdict line, and %r
(copy mode) copies the focused run's id.
To diagnose: the ⚒ Runs card¶
One block per run, newest first; ( / ) step between blocks. Selecting a node lands
on the newest block. A reader sitting on the newest block follows new arrivals; a reader
who moved back holds position and sees an arrival dot. A nested run renders as a ↳
line inside its parent block, never as its own block.
Each block shows the outcome and context lines (tool label, verdict, argv, owner), a
stage waterfall, triage items with cross-run and cross-agent witness counts, child runs,
and a bounded log tail. Three detail levels come from h / l (full / compact via H
/ L), routed by the active card — while the Runs card is active those keys drive run
detail, not folds. When the detail or the log is pruned the block says so honestly
(detail pruned · summary retained) instead of showing an empty success.
A live block progresses in place: elapsed repaints once a second, pending stages come
from the reference run, and the block settles in place when the run does — no full
refetch unless the glance drifts. v hint mode gains one ⚒ run log <8hex> target per
visible block, opening the retained log in the pager. A node with no runs and no calls
shows No tool runs or LLM calls for this node; on a remote row it says
ToolRun history lives on <machine> instead.
Links from LLM Calls, slow tools, and Context cards¶
An LLM Calls row whose command ran sase tool run (or a wrapping sase monitor start)
gains a verdict suffix that clicks through to the run's block. A sase tool run row in
the Main deck slow-tool list gains a live-stage or verdict suffix. Monitor and
named-proc Context cards gain a clickable Tool run row. The run itself is never copied
into those surfaces — they link to it. In v hint mode each linked run also offers a
⚒ run <8hex> jump target alongside the ⚒ run log targets.
Stopping a run and running a tool¶
The command palette offers Stop live tool run and Run project tool… whenever a local
live run (for stop) or a local node (for run) is in play.
Stopping asks for confirmation in a DANGER dialog with Cancel focused, and the copy
names the consequence for the owner: an inline agent run's sase tool run exits 143
(its agent keeps running and sees a failed check), a monitor-owned run's monitor stops
and its follow-up agent never launches, and a hand-off proc is killed with the run
settling as stopped. A nested run resolves to its outermost stoppable ancestor first.
The stop itself runs as a durable proc (sase tool stop RUN -j with a typed result), so
it appears in the Procs tab and survives quit.
Run project tool… confirms the argv and the root, then hands sase tool run -H <tool>
off at the current project's primary checkout root through a session worker. When that
run settles, an OpenToolRun notification arrives; selecting it jumps to the run's
Agents-tab node with its ⚒ Runs card active and the run's block selected (or opens Admin
Center → Tools focused on the run).
Plan Workflows¶
When an agent submits a plan via /sase_plan (or sase plan propose, including the
%auto:epic path), it enters a planning phase before executing:
- TALE / EPIC — The agent has submitted an authored tale or epic and is waiting for user review. Tales are pink/magenta; epics are orchid. PLAN is the compatibility fallback when the authored tier cannot be resolved.
- PLAN APPROVED — The plan has been approved and the follow-up agent has been spawned. Shown in cyan/turquoise.
- PLAN REJECTED — The plan was rejected. A no-feedback rejection from sase's TUI or
sase plan rejectwrites the rejection response first, then attempts to dismiss the notification, user-kill the matching planner, and persist dismissed-agent state so the row is hidden on refresh. If the matching row is already gone, the plan is still rejected. Rejected archived plans can still appear in history-oriented views, and redundant completion notifications are suppressed.
Plan files generated by the agent are displayed in the file panel alongside other agent artifacts. Plan approval notifications include the LLM provider and model name, so users can see which model proposed the plan (visible in both the TUI notification modal and Telegram delivery).
sase's TUI arrival toast names the authored tier — Tale ready or Epic ready —
instead of using a generic Plan label. An epic toast adds the validated phase count,
dependency wave count when available, and the non-zero per-size counts (XS, S, M,
L, and XL). Those values are captured when the approval gate is created, so a
notification that is snoozed and later resurfaces keeps its original summary even if the
bundled plan was edited meanwhile. Batched warning toasts likewise count tales and epics
separately.
When sase plan propose writes the plan, it also touches ~/.sase/.ace_refresh_pulse
to wake any running TUI immediately — the tier-aware TALE or EPIC status (or
fallback PLAN) appears without waiting for the next auto-refresh tick. The pulse file
is consumed by the inotify artifact watcher (see Auto-Refresh) and is
harmless if no TUI is open.
Root plan workflows also surface their tier-aware pending status when a re-proposed plan
is still awaiting review. Plan and feedback timestamps from feedback-round children
(--2, --3, ...; legacy -2, .2, etc.) propagate onto the root entry, and whenever
the root's latest plan timestamp is newer than its latest feedback timestamp the
override engine restores TALE, EPIC, or fallback PLAN over a RUNNING or DONE
label. This applies only to root plan workflows that have not yet spawned a terminal
follow-up (--code, --epic, ...); once a terminal follow-up is launched, the parent
moves on to PLAN APPROVED (or the matching follow-up status) instead.
The Plan Review modal title shows a provider-themed PROVIDER(model) badge between the
"Plan Review" label and the plan filename — orange for Claude, lime for Codex,
Antigravity indigo (#6E5DE7) for agy, neutral muted for other providers. The badge is
omitted when provider/model metadata is absent, leaving the legacy title shape
unchanged.
Whole-document Markdown previews in plan, launch, and custom-gate review modals highlight leading YAML frontmatter as YAML and the remaining body as Markdown. Highlighting does not alter validation or the reviewed file contents.
Plan Decisions (see Plan Decisions) add typed choices and
toggles to plan frontmatter. The modal renders them in a Decisions section above the
verdict: change a value with Space, h/l, or r, and the primary action approves
exactly the values on display. decision_<id> fields are treated as already collected,
so they do not appear as extra inputs either. Changing decision definitions through the
gate's plan editor is refused. Override a default outside the modal with
sase gate answer and --set decision_<id>=....
For tale plans, the modal's primary Approve decision includes two independently
selectable add-ons: Commit plan file to the plans sidecar and Run coder
follow-up. Both are selected by default. Press enter to approve with the current
checkbox selection, move to another decision (reject or feedback) with j / k and
submit it with Ctrl+S, or press c for Custom Approval.
sase's TUI submits the decision as a tracked sase gate answer proc labeled
Plan response: <choice>, so a slow or failed submission shows up in the Procs tab and
an error toast rather than blocking the TUI; the inbox and Agents rows refresh once the
answer is recorded.
For the Tale and Commit choices, SASE publishes the reviewed plan to the archive before
the approval response is made terminal. The response carries a canonical plan:
reference, so a coder launched from a different numbered workspace resolves the same
archived plan without depending on the approver's checkout path. If publication fails,
SASE does not write the terminal response. The PlanApproval notification remains
actionable and can be retried after the archive problem is fixed.
The same pending approvals are available from the CLI. Run sase plan to see pending
proposals, recent approvals, and inferred rejected archived plans; run
sase plan approve <name> --kind approve|commit|epic|tale or sase plan reject <name>
to write the same response protocol used by the TUI modal. Use the plan name from a
Proposed row (the id_prefix still works); if the selector is omitted, the CLI acts
only when exactly one proposal is pending. Omitting --kind uses the plan's authored
tier. In the Plan Review modal, enter uses that same authored-tier default; use
Custom Approval to pick a different outcome. approve starts the
coder without committing an SDD plan, tale commits the plan as an SDD tale and starts
the coder, epic commits the matching SDD tier and launches the bead follow-up, and
commit records the approved plan in SDD without launching a coder. -m/--model picks
the follow-up agent's model, while -p/--prompt adds extra coder instructions for the
approve and tale paths. Tale and epic choices validate the plan against the target
schema before consuming the approval; failures surface an error and keep the
notification actionable. CLI rejection also attempts the durable planner cleanup used by
no-feedback TUI rejection.
On an active agent, A toggles bare %auto, and the change takes effect at the next
gate. If auto-approval is off, it turns it on exactly as if the agent had been launched
with %auto. If any auto-approval is on (including a launch-time %auto:tale /
%auto:epic), it turns it off, and the next plan or question gate parks for review.
The agent's next submitted plan is approved at its authored tier. A tier: tale plan is
approved and committed as a tale; a tier: epic plan is approved as an epic and follows
the epic follow-up path.
The row shows ⚡ while enabled, and the footer label switches between auto-approve
and unapprove.
Like bare %auto, it also auto-settles question gates.
Plan Approval Keybindings¶
| Key | Action |
|---|---|
j / k |
Move through Actions, then Decisions, then Verdict |
Space |
Next choice (wrapping) or flip a toggle; on an AND member, toggle |
l / Right |
Next choice, or set yes |
h / Left |
Previous choice, or set no |
r |
Reset the focused decision row to ★ |
R |
Reset every decision row to ★ |
Enter |
Submit the primary (approve) decision with the values on display |
Ctrl+S |
Submit the focused decision (for example reject or feedback) |
1–9 |
Submit the correspondingly numbered decision |
i |
Open the input panel for the focused decision's note or declared fields |
c |
Open Custom Approval |
e |
Edit the plan file in $EDITOR |
y |
Copy the plan file path to the clipboard |
Y |
Copy the plan content to the clipboard |
d |
Open Gate Debug |
Ctrl+D/U |
Scroll plan content down / up |
g / G |
Scroll to top / bottom |
q / Esc |
Cancel |
The navigation, submit, and input-panel keys are the shared gate-modal keys; see Remapping Gate Modal Keys.
The question modal also supports y to copy questions and selected answers.
Custom Approval¶
Pressing c in the plan approval modal opens a custom approval dialog. Choose the
approval outcome directly: Approve, Tale, or Epic. These choices map to the same
response protocol used by external approval transports: Approve runs the coder without
asking the runner to commit an SDD plan, while Tale and Epic commit the plan under the
matching tier in the resolved SDD plans root's <YYYYMM>/ directory. The root may be
in-tree, a legacy .sase/sdd/ clone, or the split --plans sidecar;
sase repo path plans prints it.
| Key | Action |
|---|---|
Enter |
Choose the highlighted action |
a |
Highlight Approve |
t |
Highlight Tale |
e |
Highlight Epic |
m |
Select coder model |
p |
Edit additional coder prompt |
w |
Edit wait dependencies |
c |
Edit capacity (Epic only) |
Ctrl+N/P |
Next / previous action |
q / Esc |
Cancel |
The dialog keeps the custom coder prompt and follow-up model controls for Approve and Tale. Epic approval reads its land and phase models from structured plan frontmatter and launches bead work directly, so those controls are hidden for Epic:
- Additional prompt — Optional extra instructions for the coder follow-up. It is used by Approve and Tale.
- Wait for — Optional comma-separated agent names and
bead=<id>entries. Presswto edit it. The dialog validates the same grammar assase plan approve --waitbefore returning to the approval screen. Approve and Tale hold the coder follow-up; Epic holds the launched bead work. - Capacity — Epic only. Press
cto set a per-launch runner-capacity budget for the launched bead work; blank keeps the default queue behavior and1runs it alone. - Coder model — Select an LLM model for the next follow-up agent instead of using
the role default. For Approve and Tale that agent is the coder. Shows all registered
models grouped by provider (Claude, Codex, Antigravity, Qwen, OpenCode, Muse Code,
Grok Build) with a "Custom..." option for freeform input. A model its provider flags
with an advisory carries a warning-styled
⚠ <label>suffix on its row (ⓘfor informational advisories), with the full advisory sentence in the row's secondary text — this is where somebody actually chooses a model, so the trade is impossible to miss. See LLM Providers — Model advisories. Type to filter by provider, model id, label, or short alias; usej/kor arrows to navigate,Enterto select,Escto clear the filter or cancel, and'for jump hints over the visible selectable rows. The displayed default resolves to the model the handoff will actually use: for Approve and Tale, the validated tale size selects the corresponding@<size>alias directly, with legacy sizeless tales using@medium. Selecting a specific model and then re-opening the picker and choosing "Follow-up default" resets the follow-up back to that role default (distinct from pressingEsc, which keeps the current selection).
The custom approval dialog no longer exposes separate commit/run switches because the
selected outcome determines the commit location and follow-up behavior. Additional
session members are launched explicitly with %i(suffix, session=parent); they are not
selected at the plan gate.
Launch Approval¶
Launches requested by a running agent (see
Agent-initiated launches) arrive
as priority notifications with a LaunchApproval action. Selecting one opens the launch
approval modal, which renders the request's human-readable preview
(launch_preview.md). Clan slots identify their rootless clan alongside the model,
kind, and planned member name. Press a to approve, r to reject, and q or Esc to
cancel. sase's TUI resolves the same hash-verified command bundle used by mobile and
remote callbacks, while retaining legacy launch-request fallback. The CLI equivalents
are sase launch approve <selector> and sase launch reject <selector>. From inside an
agent, sase launch request creates a LAUNCH gate turn and ends the requesting turn;
the process does not wait for the response. By default, approval, rejection, timeout, or
gate/dispatch failure settles the turn and resumes the original requester as one session
successor. That successor receives the decision, reviewer feedback, typed dispatch
result, requester/workspace/session identity, and checkpoint; a stopped gate does not
resume. A request can explicitly choose terminal_handoff when every branch should end
without a requester continuation. Outside a SASE agent, terminal handoff is the default:
the command registers the gate, prints its creation descriptor, and returns. Automation
that needs the terminal gate result can then run
sase gate wait -i <request-id> -k launch -j.
Agent Holds¶
An agent hold is a durable reverse wait: while it is active, matching WAITING or
QUEUED agents, later launches, and undispatched procs are kept from starting. Holds
are armed with sase agent hold or the
%hold directive. sase's TUI shows holds in three places:
- Agents tab rows. A
QUEUEDrow parked by a hold appendsheld by <armer>after its queue position. If a held agent and the agent that armed the hold end up blocking each other, SASE also sends a Hold deadlock notification; the hold's TTL still guarantees progress, but you can release the hold or kill one side sooner. - Launch-preview confirmation. Submitting a prompt with a broad
%hold— one that combinesfuturewithscope=host, or whosependingcapture would freeze more WAITING and QUEUED agents thanagent_hold_confirm_capture_threshold(default10) — opens an Arm this hold? confirmation before anything launches. It lists each broad hold's directive and livependingcapture, plus a warning for a host-widefuturehold. The accepted prompt is already represented by a preparing proc row. Cancelling restores it when safe, or stashes it when another prompt or modal has focus; a standalone hold confirmation defaults to Cancel. Narrow holds launch without asking. During the current beta, pressing Arm only confirms the launch preview; it does not write a hold to the store. - Holds pane. Open SASE Admin Center with
#, then Config > 03 Holds (0then3; 02 whenadmin_center_flagsis off). Each row shows the armer and its kind, the scope (hostorproject:<name>), the selectors (names=,hoods=,tribes=,future, andpending=N), and the time until expiry. Pressj/k(or the arrow keys) to move,dto release the highlighted hold,rto reload, andqorEscto close Admin Center.
Linked Chats in Multi-Step Workflows¶
When a workflow spawns multiple agents (e.g., a planner step followed by a coder step),
the chat history files for each step are cross-linked via a ## Linked Chats markdown
section. This section is inserted near the top of each chat file and lists all related
agents with their roles and file paths, making it easy to trace the full workflow from
any individual agent's chat history.
For example, a plan-then-code workflow produces chat files with:
## Linked Chats
- **1. planner** — `/path/to/planner_chat.md`
- 2. coder — `/path/to/coder_chat.md`
The current agent's entry is bolded for quick identification.
Retry/Fallback Display¶
When an agent encounters a retryable error (configured via llm_provider.retry), the
Agents tab shows retry state:
- RETRYING — Shown in bold orange when waiting before the next retry attempt.
Includes a countdown timer:
RETRYING (45s). - ↻N — Shown after the status for running agents that have retried. The number
indicates how many retries have occurred (e.g.,
↻2means two retries so far). - ▸Model — Appended to the retry annotation when the agent has fallen back to an
alternate model (e.g.,
↻3▸flash).
Prior Agent Attempts¶
Every time the axe retry loop retries an agent — context-limit retry, provider/API-error
retry, user-configured retry, or fallback-model switch — the failed attempt's partial
reply, error text, timestamps, and model are snapshotted under
<artifacts_dir>/attempts/<N>/. The AGENT REPLY area in the Agents tab renders these
prior attempts inline with styled dividers before the current/final attempt, so the full
arc of the agent's work stays visible in one scroll.
sase's TUI hydrates prior-attempt history lazily. Normal Agents-tab refreshes do not
enumerate every attempts/<N>/ directory; the selected detail panel, D attempt-view
toggle, and content search hydrate the needed attempt records on demand.
Press D to collapse the view to the current attempt only; press D again to
re-expand. The binding only appears in the keybinding footer when the selected agent has
one or more prior attempts.
Custom Keymaps¶
All TUI keybindings are configurable via the ace.keymaps section in sase.yml. You
can remap app-level, gate-modal, Memory-panel, and focused Statistics-pane keys and
define entirely new prefix-key modes.
Remapping Built-in Keys¶
Override any app-level keybinding under ace.keymaps.app:
ace:
keymaps:
app:
next_patch: "n" # Remap j -> n
prev_patch: "p" # Remap k -> p
edit_query: "f5" # Structured query on Artifacts and Agents
show_notifications: "N" # Remap i → N
Agents deck search is independent: remap
ace.keymaps.modes.leader_mode.keys.search_forward to change the subkey after the
configured leader prefix. Structured query editing on every query-capable surface,
including Agents, is ace.keymaps.app.edit_query. Stale app.search_forward and
leader_mode.keys.edit_query overrides are ignored with a warning pointing at those
replacements.
Remapping Statistics Pane Keys¶
Override focused Statistics bindings under ace.keymaps.statistics:
ace:
keymaps:
statistics:
prev_view: "left_square_bracket"
next_view: "right_square_bracket"
select_view: "0"
jump_to_entry: "apostrophe"
cycle_range: "f11"
cycle_range_reverse: "shift+f11"
custom_range: "c"
cycle_group: "g"
cycle_project_filter: "p"
cycle_project_filter_reverse: "P"
focus_macro: "x"
clear_macro_focus: "X"
scroll_down: "ctrl+d"
scroll_up: "ctrl+u"
refresh: "f10"
help: "f9"
The example above names every Statistics binding, but only some are remapped: it shows
cycle_range, cycle_range_reverse, refresh, and help moved off their t, T,
r, and ? defaults, and repeats the defaults for the rest. Override only the keys you
want to change.
These keys dispatch only while the Admin Center Statistics pane is focused. They may
overlap app-level bindings without creating a global conflict, and the pane's hint bar
always shows the effective keys. Press the configured select_view prefix and then
1–8 to select the matching view displayed as 01 through 08; bare digits continue
to switch the Admin Center's top-level tabs. jump_to_entry arms that same
numbered-view selection, which is how the Admin Center-wide ' behaves on a pane that
has no row cursor to jump between — the visible strip numbers serve as its hints. The
group control is visible and active only in Projects, Macros, and Perf. On the Macros
view, the focus key opens a filterable picker and the clear-focus key restores All
macros. Project filtering cycles through All projects and the latest cached
unfiltered ranking: the configured forward key moves toward the first ranked project,
the reverse key moves toward the last, and both wrap. First open seeds the current
project when ace.current_project.seed_filters is on; either key can cycle away from
that seed. Either key clears an active project filter directly when its loaded result is
empty.
Remapping Gate Modal Keys¶
Override the shared plan/custom gate controls under ace.keymaps.gate:
ace:
keymaps:
gate:
next_control: "down"
previous_control: "up"
toggle_option: "space"
submit_primary: "enter"
submit_branch: "ctrl+enter"
open_inputs: "o"
next_input: "ctrl+n"
previous_input: "ctrl+p"
decision_next: "l,right"
decision_prev: "h,left"
decision_reset: "r"
decision_reset_all: "R"
These bindings dispatch only while a branch-driven gate modal is open, and its footer
shows the effective keys. The example remaps navigation, submit_branch, and the input
panel keys, and repeats the defaults for toggle_option and submit_primary.
open_inputs (default i) opens the input panel for the focused option's note or
declared fields, including an optional note that would otherwise skip the panel.
next_input and previous_input (defaults tab and shift+tab) walk the panel's
fields and buttons; they are inactive on the gate modal itself. Confirming the panel
submits the branch; cancelling it returns to the gate without answering. The retired
activate_control setting is accepted as a deprecated alias for submit_primary.
Remapping Memory Panel Keys¶
Override Memory panel bindings under ace.keymaps.memory. A value may
list more than one key, separated by commas:
ace:
keymaps:
memory:
follow_link: "enter,l"
travel_back: "backspace,h"
filter_notes: "slash"
toggle_body_filter: "greater_than_sign"
toggle_web: "space"
next_strand: "s"
prev_strand: "S"
add_note: "a"
edit_note: "e"
delete_note: "d"
publish: "I"
These bindings dispatch only while the panel is open. The full action list and defaults
are in the ace.keymaps configuration reference. .
(full_stop) is reserved for the fixed . then 1–9 numbered-chip prefix; a config
that assigns it to a Memory action is warned and reverted to that action's default. >
(greater_than_sign) remains a valid configurable key and is the default for
toggle_body_filter.
The retired Glossary panel's bindings folded into these Memory panel actions:
add_note/delete_note branch on the selected row (a web row adds a strand, a strand
row deletes one), and next_link/prev_link/follow_link follow a glossary strand's
mention-relation chips the same way they follow a note's PARENT/CHILDREN chips.
ace.keymaps.glossary is still accepted in config for one release, but it is inert: no
defaults ship for it and it builds no bindings. sase doctor warns when a loaded config
layer sets it explicitly and points at ace.keymaps.memory instead.
Remapping Snippets Panel Keys¶
Override Snippets panel bindings under ace.keymaps.snippets. A
value may list more than one key, separated by commas:
ace:
keymaps:
snippets:
follow_relation: "enter,l"
travel_back: "backspace,h"
filter_snippets: "slash"
toggle_body_filter: "full_stop"
add_snippet: "a"
edit_snippet: "e"
delete_snippet: "d"
These bindings dispatch only while the panel is open. The full action list and defaults
are in the ace.keymaps configuration reference.
Custom Modes¶
Define user-defined prefix-key modes under ace.keymaps.modes. Each custom mode has a
prefix key and a keys dict where each sub-key specifies either a shell command or
a built-in action:
ace:
keymaps:
modes:
my_mode:
prefix: "B"
keys:
run_tests:
key: "t"
shell: "just test"
show_log:
key: "l"
shell: "git log --oneline -20"
refresh:
key: "r"
action: "refresh"
Pressing B activates the mode, then pressing t runs just test, l shows the git
log, etc.
Validation¶
The keymap loader validates all configuration:
- Invalid keys are reverted to their defaults with a warning
- Duplicate keys within one binding scope are detected and the conflicting override is reverted
- Stale
app.search_forwardandleader_mode.keys.edit_queryoverrides are ignored with a warning; they are not translated into a second live shortcut - Prefix conflicts between custom mode prefixes and existing app bindings are warned
- Key names follow Textual's spelling (
slash,dollar_sign,backslash,vertical_line, …); the raw glyphs+,-,$,_,\, and|are also accepted, and key hints display the glyph
See docs/configuration.md for the full ace.keymaps configuration
reference.
Prompt Input Widget¶
The prompt input is a multiline TextArea widget with vim-style INSERT, NORMAL, VISUAL,
and V-LINE modes. The widget provides markdown syntax highlighting for prompt content
(headings, bold, italic, code blocks, lists, etc.). The first dash of an unindented or
space-indented - bullet, and the digits plus delimiter of an unindented or
space-indented <N>. / <N>) ordered marker, are additionally bolded with the same
theme-aware accent, including inside fenced code; this presentation does not change the
prompt text. A tab-indented dash or ordered marker is not treated as a list marker.
Known macro syntax is layered over the Markdown colors using the active theme: #macro
references are bold in the theme's success color, %directives are bold in its warning
color, /skill references use a tint of the accent color, and --- separators are
dimmed. Argument text is split into parts instead of one flat color — delimiters such as
:, (, ,, and ) and the = sign are muted, keyword names use a lighter tint of
the owning reference's color, and values are tinted by type (strings, numbers, and
booleans each get their own hue). Directive arguments get the same treatment in the
directive palette. An argument that names an unknown keyword, repeats a keyword, or has
a value of the wrong type is additionally underlined.
When loaded prompt text contains literal top-level --- multi-agent separators, sase's
TUI renders the text as a prompt stack: one pane per agent segment. YAML frontmatter at
the start stays prompt-level metadata, and --- lines inside fenced code blocks are
left alone. A #name macro swarm invocation stays a single pane and expands only when
it is launched. During live editing, typed --- lines stay literal text; add prompt
panes with g- in prompt NORMAL mode. The detailed multi-agent parsing rules live in
the Macro reference.
Cursor Readout¶
Every mounted pane advertises its cursor position as Ln <line>, Col <column>, both
1-based and counted in document columns (the character index within the logical line,
not the soft-wrapped screen column). The active pane's readout sits flush right on the
bar's bottom border, next to the mode hints; each parked pane's readout sits on the
right end of its own ─── ▍ agent N ─── separator rule. The digits are painted the
color of that pane's own vim-mode cursor -- gold for NORMAL, cyan for INSERT, magenta
for VISUAL / V-LINE -- so the readout and the cursor it describes always match. On a
narrow terminal the active-pane readout always wins over the mode hints (which truncate
first) and the prompt-search match pill; a parked pane's readout is dropped entirely
rather than abbreviated if its separator cannot fit both the readout and the agent N
label.
Prompt Search¶
In prompt NORMAL mode, / and ? open an incremental search panel for forward and
reverse searches. As you type, matches are highlighted in the active pane and the
panel's right edge shows the selected stack-global count, such as 2/3. Enter accepts
the current local match and keeps the highlights; Esc or Ctrl+C cancels the search,
restores the origin cursor, and clears the highlights. If a query has matches elsewhere
in the prompt stack but none in the active pane, the panel says
no match in this pane · N in stack; accepting still does nothing until the active pane
has a local match.
After a search is accepted, the count moves to a compact two-tone pill on the prompt
bar's bottom border, just left of the Ln, Col readout. The query chip is graphite and
uses the accent color for the search sigil; the count chip is solid warning gold, the
same color as the current-match highlight, with the current ordinal in bold. The chip
ink is chosen for maximum contrast from the theme's own foreground and background while
avoiding colors that 256-color terminals with base16 palettes repaint. The sigil records
how the search was made: / for forward, ? for reverse, * for whole-word forward,
and # for whole-word reverse. Non-whole-word word searches (g*, g#, and VISUAL *
/ #) use / or ?.
n and N repeat the recorded search across every non-auxiliary prompt pane in stack
order, with the existing wrap toasts when the traversal crosses the top or bottom. *,
#, g*, and g# search from the word under the cursor; VISUAL * and # search the
selected text. The pill's number is always stack-global and follows the highlighted
match, even if you move the cursor away afterward. It disappears with the highlights:
Esc, entering INSERT, edits, pane switches, and starting a new search all clear it.
See Operator + Search for operating up to a match with d/, d?,
dn, and dN.
On narrow terminals, the bottom border keeps the cursor readout first. The mode hints
truncate or drop before the search pill; then the pill drops its query segment and keeps
only the count; if even that cannot fit, only Ln, Col remains.
INSERT Mode (Default)¶
| Key | Action |
|---|---|
Enter |
Open the submission panel for a non-empty agent prompt; pressing Enter again confirms the primary launch action. Does not accept a completion candidate |
Ctrl+S |
Stash the active pane; from an empty prompt, open the Prompts overlay on Stash |
Ctrl+C |
Cancel the prompt; in a prompt stack, cancel only the selected pane |
Ctrl+J |
Insert a newline; continue a containing - bullet or <N>. item (renumbered), or leave the list from an empty marker |
Ctrl+A |
Move to start of line (jumps to previous line start if already at col 0) |
Ctrl+E |
Move to end of line (jumps to next line end if already at end) |
Ctrl+F |
Accept a highlighted completion-menu candidate; with no menu, take the whole inline ghost when the rest of the line is blank, or move one character forward |
Ctrl+G |
Start the prompt-local prefix (g or Ctrl+G again opens $EDITOR) |
Ctrl+G Enter |
Submit only the selected pane |
Ctrl+G j/k |
Focus the next / previous pane and leave the target pane in INSERT mode |
Ctrl+G J/K |
Move the active pane down / up and leave it in INSERT mode |
Ctrl+G - |
Add an empty bottom pane |
Ctrl+G G |
Open the Memory panel; seeds from the glossary term under the cursor when there is one |
Ctrl+G m |
Open the Memory panel; seeds from the #memory/<stem> reference under the cursor when there is one |
Ctrl+G D |
Choose a local or eligible enrolled launch target and update the pane's %dispatch selector |
Ctrl+G d |
Edit the macro definition under the cursor in the prompt bar |
Ctrl+G f |
Reformat the active prompt pane's Markdown with Prettier |
Ctrl+G w |
Write a bound macro definition; unbound drafts fall through to save-as |
Ctrl+G = |
Show/focus the macro frontmatter panel; its rows-mode g= returns to the originating pane |
Ctrl+G s |
Bundle every non-empty pane into one stash row |
Ctrl+G S |
Overwrite a pinned stashed prompt with the current stack |
Ctrl+G x / Ctrl+G Ctrl+X |
Open, retarget, or edit an existing mini-macro pane |
Ctrl+G t / Ctrl+G Ctrl+T |
Open, retarget, or edit an existing snippet pane (see Authoring a snippet from the prompt bar) |
Ctrl+G X |
Save as reusable macro/snippet; macro mode converts raw <tags> |
Ctrl+G L |
Convert the active pane into a frontmatter-local macro; raw <tags> become inputs |
Ctrl+G Ctrl+C |
Cancel every pane in the prompt stack at once |
Ctrl+G p |
Open the Prompts overlay on Stash |
Ctrl+G r |
Open the recent-files history menu (recently referenced files and @kind:payload references) |
Ctrl+Y |
Open the workflow YAML editor |
Ctrl+K |
Open the Prompts overlay on History from a single-line prompt, scoped to that prompt's project (see Prompts Overlay) |
Ctrl+P |
Cycle toward older workspace MRU prefixes (no-prefix stop before wrapping); in a macro keyword slot, open the keyword menu at its last row |
Ctrl+N |
Cycle toward newer workspace MRU prefixes (no-prefix stop before wrapping); in a macro keyword slot, open the keyword menu at its first row |
Ctrl+T |
Completion (structured tokens, paths, prompt-local words, history words, or next-word ghosts and border peeks; at a whitespace boundary it requests next words instead — recent files moved to Ctrl+G r; a second press accepts the highlighted word-menu row or takes one ghost or peek word; see Completion) |
Ctrl+R |
Recursive fuzzy file finder using the same prompt-aware path root as file completion |
Tab |
Expand a snippet or advance its tabstop; otherwise indent a bullet or nest an ordered item under a preceding marker |
Shift+Tab |
Retreat to the previous snippet tabstop; otherwise dedent a bullet or unnest an ordered item into its enclosing run |
#@ |
Open Macro snippet picker (type # then @) |
Escape / Ctrl+] |
Switch to vim NORMAL mode; Ctrl+] is the race-free alternative when typing following NORMAL commands quickly |
In prompt INSERT mode, sase's TUI auto-pairs safe openers for (), [], {}, <>,
single quotes, double quotes, and backticks. Typing the matching closer over an
auto-inserted closer moves the cursor across it instead of duplicating it, and backspace
or delete removes both sides of an empty pair. Pairing is conservative: it is suppressed
before token characters, when text is selected (the typed character replaces the
selection literally), for contractions or possessives, and for repeated quotes/backticks
needed to type Markdown fences or code spans.
When ( is typed immediately after a macro or supported directive argument delimiter,
the prompt input normalizes the shorthand in one keyboard edit. A single colon is
removed (#review: -> #review()), while :: followed only by ASCII spaces is moved
after a complete pair (#review:: body -> #review():: body) with the caret inside the
parentheses. Typing ( immediately after a closed macro argument list continues the
list by adding a comma and placing the caret before ), for example
#review(path=a):: body becomes #review(path=a,|):: body; the remaining macro
argument menu opens when auto_macro_menu is enabled. Empty lists and lists that
already end in a comma only move the caret. The double-colon form preserves the exact
spaces and suffix text; tabs, newlines, nonbreaking spaces, directive argument lists,
selected text, and literal regions keep ordinary insertion behavior.
INSERT-mode Ctrl+J and prompt NORMAL-mode o / O continue a containing
space-indented - bullet using that bullet's indentation. Prompt NORMAL-mode J is
the inverse operation: when it folds the next line into a nonblank current line, it
drops that line's supported - marker. Bullet continuation also works from physical
continuation lines, including Prettier-wrapped nested bullets; non-bullet lines keep the
ordinary bare newline or open-line behavior. In INSERT mode, when there is no selection,
pressing Ctrl+J anywhere on a line containing only zero or more leading spaces
followed by - replaces that marker with a bare newline and moves the cursor to column
zero, ending the list -- but only when the line above that marker is itself part of a
hyphen bullet. The common sequence is therefore Ctrl+J once to create the next sibling
marker and Ctrl+J again to exit the list. A marker-only line whose preceding line is
not part of a bullet -- a freshly typed - on the first line, or one following a blank
line or plain prose -- grows a sibling marker on the next line instead, so the exit
still happens on the following press. Those two edits are separate undo checkpoints. A
selection uses the normal replacement path instead. Extra spaces after the marker, tab
indentation, other Markdown markers, and markers containing text do not trigger either
path.
Ordered items (<N>. or <N>), one to nine digits) mirror every one of those hyphen
rules for Ctrl+J, o, O, and J, and add the one thing ordered lists need: after
each structural edit, sase's TUI renumbers the surrounding run -- the maximal sequence
of same-indent, same-delimiter siblings, joined across blank lines and each item's own
owned continuation lines -- so the live numbers agree with what gf (Prettier
formatting) would produce. When a run's second item repeats the first item's number,
every item in the run keeps that number (the 1. / 1. / 1. convention Prettier
preserves); otherwise later items are numbered sequentially from the first item's start.
Ctrl+J, o, and O give a newly inserted item the number after its nearest preceding
sibling, or the run's first number when there is none. J drops the pulled-up marker
and renumbers the run it left behind. A renumber that changes a marker's width (9. ->
10.) shifts every line that item owns by the same amount so indentation stays correct,
and leading zeros (007.) are recognized as a marker but always renumber to plain
decimal.
In INSERT mode, Tab and Shift+Tab do snippet work before list shifting. Tab first
expands a trigger word immediately before the cursor, then advances to the next live
snippet tabstop. Shift+Tab first retreats to a previous live tabstop. When that
snippet action reports no movement or expansion, the selection is collapsed, and the
cursor is anywhere on a direct marker line beginning with zero or more spaces followed
by -, sase's TUI indents or dedents that bullet. Each press shifts only that logical
line by the same two-space unit as vim >> / <<; dedent removes up to one unit, and
the cursor follows the shifted content. Physical continuation lines, tab indentation,
and other Markdown marker styles are excluded.
The same fallback applies to ordered items from anywhere on the direct <N>. / <N>)
marker line. Ordered Tab nests at the content column of the nearest preceding marker
line (either session, same or lower indent) instead of a fixed two-space unit, because
an ordered item can only interrupt its parent's paragraph when numbered 1: Tab with
no preceding marker line to nest under is a no-op, Tab landing under an existing
nested run continues that run at its next number, and Tab that starts a new nested
list numbers the moved item 1. Shift+Tab moves the item back out to its parent's
indent and gives it the next number in that outer run; at the outermost level it is a
no-op. Both carry the item's owned block along and renumber the source and destination
runs as one undo checkpoint.
Text automatically wraps at the terminal width, breaking at spaces (never mid-word). Line numbers appear in cyan when the text exceeds one line. The native cursor cell is color-coded by prompt Vim mode: INSERT uses cyan, NORMAL uses gold, and VISUAL or V-LINE uses magenta.
Uppercase TODO at identifier boundaries is a visual draft marker. sase's TUI gives
TODO, TODO:, TODO(owner), and TODO(owner): headers the exact #FFD700 gold used
by the Agents-tab RUNNING status with explicit deep navy #00005F text. The deep navy
stays legible on gold without relying on the terminal's customizable ANSI black palette
entry. Only a header ending in : activates the quiet, theme-aware warm italic
annotation style for the rest of that line; punctuation and prose after bare TODO or
TODO(owner) retain their ordinary prompt syntax. When the first content in a dash-list
item is the exact TODO: header, the body style continues through lazy and indented
continuation lines, nested list content, and later paragraphs that Markdown assigns to
that item. It stops at the sibling-item or outside-content boundary, and structural list
dashes keep their bullet color. Checklist prefixes, TODO(owner):, and a TODO: later
in item prose retain the same-line behavior.
Inline backtick spans and closed or live unclosed backtick/tilde fenced code blocks are
literal zones: TODO-shaped text inside them receives no marker or body treatment and is
omitted from the count. Ordinary quotation marks are not code delimiters. Lowercase
todo and identifiers such as TODOS, TODO2, and preTODO remain ordinary text.
When markers exist, the prompt border shows a matching deep-navy-on-gold TODO N count
pill for every non-literal match across the full prompt stack, including compact
inactive panes and markers outside the active viewport. The pill disappears immediately
when the last marker is edited away.
TODO treatment does not move the cursor during history or stash restoration, and sase's
TUI stashes and opens the literal prompt text in $EDITOR unchanged. Submitting an
agent prompt with one or more visible TODO markers opens a neutral y/n confirmation with
Keep editing focused by default. Keeping the draft preserves the exact prompt or
prompt stack without launching or writing history; approving launches the same literal
prompt text unchanged. The warning uses the same detector as the gold marker and count
pill, so TODO-shaped text in inline or fenced code, lowercase todo, and non-boundary
identifiers such as TODOS, TODO2, and preTODO do not trigger it. Feedback and
coder-prompt submission keep their existing unguarded behavior. Only the
colon-terminated body-note color follows the active dark or light theme, while the
shared deep-navy-on-gold header and count pill remain fixed; search matches, selections,
yank feedback, and the cursor retain their higher-priority treatments.
Raw Placeholder Inputs¶
Raw <placeholder> tags in sase's TUI prompt bar act like ad hoc prompt inputs. When
you submit a prompt containing one or more highlighted raw tags, sase's TUI opens the
Prompt Inputs panel before launch. The panel lists each unique tag once, shows a
one-line context snippet and an occurrence count, and collects values on the same page
as any required frontmatter-declared input: arguments. After confirmation, sase's TUI
substitutes the collected values into the prompt and records history for the resolved
prompt that the agents actually received.
Inline backtick spans, fenced code blocks, and %macros_enabled:false regions are
literal zones. Tags inside those zones are not highlighted as raw placeholders, recorded
in the saved common-placeholder store, or collected on submit. Their text is still
offered as a current-prompt completion candidate, ranked after live tags. Use backticks
when a tag-like value is meant to survive literally, for example
keep `<div>` unchanged.
Each raw placeholder row must be filled before launch unless it is marked literal. Press
Ctrl+L in the Prompt Inputs panel to toggle keep literal for the focused
placeholder row; when focus is outside the field list, Ctrl+L marks all still-empty
placeholder rows literal. A literal row counts as filled and leaves its original
<placeholder> text in the launched prompt.
Set ace.prompt_inputs.collect_raw_placeholders: false to stop collecting raw tags on
submit; declared frontmatter inputs are still collected. Set
ace.prompt_inputs.macro_placeholder_args: false to keep live raw tags literal when
using gX, gL, or a fresh gx extraction and mint no placeholder-derived text
inputs; Jinja-variable inference for gL still runs. See
Raw Prompt Placeholders for the exact conversion
and naming rules.
Launch Target Picker¶
From any ordinary prompt pane, press gD in NORMAL mode or Ctrl+G D in INSERT mode to
open Launch Target. The first row is here, followed by enrolled aliases in
alphabetical order. Local enrollment data labels every non-quarantined remote ok and
enables it; quarantined rows remain visible with their diagnostic context but are
disabled. This is a local eligibility catalog, not a live gateway health check; use the
Admin Center Machines tab's s action when current reachability matters. Move with j
/ k and press Enter, or select one of the first ten rows directly with 1–9 /
0.
Choosing a remote inserts or replaces the pane's single %dispatch:<alias> selector.
Choosing here removes it. The prompt context line appears while the pane has a
%dispatch selector: a valid one shows the cached Target and Source, and for a remote
also states that source proof is checked on submit; an invalid one shows Target error
with the reason. It stays hidden for ordinary local launches, unless the pane carries an
invalid %auto spelling: that shows an Auto error segment with the same message the
launch path raises, and Enter does not submit until the spelling is fixed. On
submission, the prompt bar closes immediately and source proof runs off the UI thread as
the first pending launch stage. A failed proof restores the prompt with
source blocked: <reason> in its context line (or saves it to the stash if another
prompt or modal now owns the screen).
After source preflight passes, sase's TUI inserts a provisional QUEUED remote row
before the background launch settles. A structured accepted owner response keeps it
QUEUED; a settled response changes it to STARTING. If the launch finishes without a
structured dispatch result—including the current rejection and failed-receipt paths—the
row becomes outcome-unknown WAITING; use Agents: check dispatch launch outcome
from the command palette. Once the owner's catalog exposes the matching logical or exact
locator, sase's TUI removes the provisional row in favor of that authoritative row. The
Remote Dispatch Runbook explains the portable
source requirements and remote operation model.
Prompt Stacks¶
Prompt stacks are sase's TUI editing surface for literal --- multi-agent prompts.
Loading multi-agent prompt text from history, a whole-bar editor session, or an editor
buffer that returned with a @ review marker splits top-level --- segment separators
into panes labeled agent 1, agent 2, and so on; the border title shows
Prompt · N agents. Restoring stashed prompts and using marked-agent ,x can also open
a stack, but those paths load one pane per selected draft or agent instead of re-parsing
each pane's text. Panes are ordered top-to-bottom for whole-stack submission. The bottom
pane is active by default so you can keep drafting the newest segment; it is not a
priority marker, and pressing Enter immediately opens the submit chooser.
Inactive panes stay compact, and the active pane takes the available height; each parked
pane's separator rule also carries a live cursor readout of that
pane's own position. A --- line typed while INSERT mode is active stays literal prompt
text; use Ctrl+G - while drafting, or g- from prompt NORMAL mode, to add a new
bottom pane. Ctrl+G g and Ctrl+G Ctrl+G open the whole stack in $EDITOR when the
bar already has multiple panes (a single-pane bar opens just the current prompt).
Returning from a whole-bar editor session, or from a single-pane editor buffer with a
@ review marker, reloads macro-style Markdown and parses --- separators into fresh
panes. History loads parse only real multi-agent prompts; a single history item with
leading YAML frontmatter stays one verbatim pane instead of auto-opening the Frontmatter
Panel.
A single-pane editor session normally launches the moment you close $EDITOR. To review
it in the prompt bar first, end any line of the buffer with the exact suffix @ (a
space followed by @). On return, that marker is stripped from every matching line and
the cleaned text reloads with editor-file semantics: leading macro frontmatter is lifted
into the Frontmatter Panel and real --- separators split into one pane per agent, so a
marked multi-agent buffer comes back as a reviewable stack instead of launching. The
marker is editor-return-only — typing @ in the prompt bar and submitting carries no
special meaning. (This replaces the removed %edit directive.)
In prompt INSERT mode, pressing Ctrl+G opens the same context-aware hint row as prompt
NORMAL mode's g prefix, plus the editor continuation. Press Esc while the prefix is
pending to cancel it and stay in INSERT mode.
In prompt NORMAL mode, pressing g opens a small hint row for the prompt-local g
prefix actions currently available.
| Key | Action |
|---|---|
Enter |
Open the submit chooser by default; Enter inside that panel confirms the primary action. With ace.prompt_submission.confirm_on_enter: false, submit the active pane immediately |
Ctrl+S |
Stash the active pane; from an empty prompt, open the Prompts overlay on Stash |
g<enter> |
Launch the selected pane and remove it from the stack |
Ctrl+C |
Record the selected pane as cancelled history and remove it; the final remaining pane cancels normally |
Escape |
Enter NORMAL mode for stack navigation |
gj / gk |
Focus the next / previous pane in NORMAL mode; inside the panel, jump to the top / bottom prompt pane |
gJ / gK |
Move the active pane down / up in NORMAL mode; reorder cycles at the stack edges |
g- |
Add an empty bottom pane in NORMAL mode and switch it to INSERT mode |
gG |
Open the Memory panel; seeds from the glossary term under the cursor when there is one |
gm |
Open the Memory panel; seeds from the #memory/<stem> reference under the cursor when there is one |
gD |
Choose a local or eligible enrolled launch target and update the pane's %dispatch selector |
gT |
Open the Snippets panel; seeds from a bare trigger or #[trigger] under the cursor when one can be resolved without I/O |
g= |
Show/focus the macro frontmatter panel; in panel rows mode, return to the originating prompt pane |
gs |
Bundle every non-empty pane into one stash row and dismiss the prompt bar |
gS |
Overwrite a pinned stashed prompt with the current stack, leaving the bar open |
gw |
Write a bound macro definition; unbound drafts fall through to save-as |
gd |
Edit the macro definition under the cursor in the prompt bar |
gf |
Reformat the active prompt pane's Markdown with Prettier |
gx |
Open, retarget, or edit an existing mini-macro pane |
gt |
Open, retarget, or edit an existing snippet pane (see Authoring a snippet from the prompt bar) |
gX |
Save as reusable macro/snippet; macro mode converts raw <tags> and leaves the bar open |
gL |
Convert the active pane into a frontmatter-local macro; raw <tags> become inputs |
Submitting one pane at a time re-attaches prompt-level frontmatter to the launched pane
so local macros and metadata continue to resolve. Empty selected panes are dropped
without launching. Whole-stack submission joins panes in top-to-bottom order and then
uses the usual multi-agent launch path, including %wait, %id, %model, and other
segment-local directives. A selected-pane TODO warning counts only that pane; a
whole-stack warning counts visible markers across all non-empty submitted panes.
Choosing Keep editing, n, Escape, or q leaves pane order, selection,
frontmatter, and source binding intact. Segment order alone does not make later agents
wait; add %wait to a later pane when it must start after the immediately preceding
submitted pane succeeds. That bare wait binding also applies when the wait comes from a
frontmatter-local or file-backed macro referenced by the pane. Explicit waits, %queue,
and waits inside literal code/disabled regions keep their normal meanings.
The Enter submit chooser accepts Enter, a, or Ctrl+S for all panes, c for the
current pane, and Esc/q to cancel without changing the stack. For a single ordinary
prompt, the panel contains one Launch agent row and Enter/s launches it. Set
ace.prompt_submission.confirm_on_enter: false to skip this panel; then plain Enter
immediately submits an ordinary single pane, the active pane of a stack, or a targeted
pane. Outside that chooser, Ctrl+S is always an active-pane stash shortcut.
Prompt stashes are a per-user draft pile stored outside prompt history. Ctrl+S
captures the selected non-empty pane plus the shared prompt frontmatter; when other
panes remain the bar stays open, and when the last pane is stashed the bar closes
without also recording the draft as cancelled history. If the active pane is empty,
Ctrl+S opens the Prompts overlay on Stash instead. gs captures all non-empty panes
in their current order as one bundled stash row and dismisses the bar. gS opens an
update flow for an existing pinned stash and overwrites the chosen row with the current
non-empty panes. Manual and restart stashes remember the active pane and its cursor, and
restore focuses that pane in INSERT mode at the same logical line and column. Legacy
rows and failed-launch recovery rows have no saved position, so they still restore to
the last pane at end of text. gX and Ctrl+G X open one save screen containing the
name, storage location, resolved path, and a live preview when the name collides. Inside
that screen, Ctrl+X switches between macro and snippet mode. Ctrl+T remains manual
completion in the prompt input and does not toggle this save screen. A successful
whole-stack save binds the prompt stack to that source. gw then performs atomic
write-back, and if the source changed since load it offers overwrite, reload, or save-as
instead of clobbering it. gd loads the simple macro under the cursor for the same
bound editing loop. gx first shows the location picker and
then opens or retargets one focused mini-macro pane. Press e there to fuzzy-find an
existing definition and edit it in place, or override a read-only one into a writable
destination. Saving that pane publishes the definition without binding the surrounding
prompt stack. gL converts the active pane through a prefilled frontmatter ghost row
and rewrites the pane to invoke the committed helper. Before gX opens the save
preview, its macro version converts live <label> tags into required Jinja text
inputs; switching that screen to snippet mode shows and saves the original active-pane
body instead. A fresh gx extraction applies the same raw-placeholder conversion to the
copied origin-pane body before the mini pane opens and seeds the mini definition's
inferred inputs. Raw placeholders typed later in the mini pane are saved as edited; the
mini save review does not run another conversion pass. gL also applies the conversion
when it creates a frontmatter-local helper. Set
ace.prompt_inputs.macro_placeholder_args: false to disable these conversions while
preserving gL Jinja-variable inference. gw only writes the currently bound
definition—it does not reinterpret newly typed raw placeholders. Tags in inline code,
fenced code, and disabled macro regions stay literal throughout. See
Raw Prompt Placeholders for the exact launch,
conversion, and naming rules.
Ctrl+G p opens the Prompts overlay on the Stash tab from the prompt bar. From the main
sase's TUI tabs, when you are not typing in a text field, @ acts on Stash. If the open
bar is Plan Feedback or Coder Prompt, @ warns
Restore is only available for agent prompts and stops. Otherwise a lone pinned draft
is loaded into the prompt bar and stays in Stash. A lone unpinned draft is loaded and
then removed from Stash; it is not moved to Trash. When Stash is empty or holds more
than one draft, @ opens the overlay on Stash instead of loading one. ,@ or clicking
the stash: chip always opens that overlay, including when exactly one draft is stashed
and when Stash is empty. The empty placeholder says to stash the current prompt. When
Trash has rows, it also names that count and says to press t to view. [ and ]
toggle between the Stash and History tabs with wraparound, so ] from Stash opens
History. Trash is a view of the Stash tab, not a third tab. Press t on Stash, or
select the 🗑️ chip, to open it. From Trash, t or Esc returns to the Stash list. [
or ] from Trash opens History, and the bracket that returns to the Stash tab brings
Trash back, because the tab remembers which view was open.
In the Stash tab, space toggles a row's persistent pin, Tab toggles a row's restore
mark, d marks one row for discard, D marks every row for discard, a toggles
restore marks on all rows, and Enter confirms the marked set; restores are pin-aware,
so marked pinned rows stay stashed. With no explicit marks, Enter restores the
highlighted row; pinned rows stay stashed when restored, while unpinned rows are removed
from Stash and are not moved to Trash. Press y to copy the highlighted row's exact
prompt body through the shared nonblocking clipboard path (OSC 52 plus a manual-copy
fallback): the payload is the stored body verbatim — multiline and bundled ---
content, whitespace, Unicode, and raw references included, without frontmatter or
preview metadata — and the panel stays open with its highlight, scroll position, pins,
and staged marks intact. A row with an empty body copies that empty string; with no
highlighted row, y is a no-op.
Number keys 1-9 and 0 restore rows 1-10 directly with the same pin-aware behavior.
@ on the Stash tab restores the newest draft, so @@ pops the latest stash when
several are stashed. On the Stash list, Escape or q closes the overlay and discards
unconfirmed marks. Confirming discards for only some rows keeps the overlay open and
repaints it in place; discarding every row, or combining discards with restores, closes
the overlay. A small stash: ≡ N pink-chip top-bar group shows how many restorable
drafts are currently stashed; the Trash count appears as the 🗑️ chip on the Stash tab,
expanding to Trash N/LIMIT in the Trash view.
Every Prompts overlay entry point — including the History ones (Ctrl+K, ,., ,>) —
first reads the stash store. If that read fails (for example a stale core binding, or a
parse or lock error), an error toast beginning Failed to read stashed prompts names
the cause and no overlay opens.
Discarding a Stash row moves it to the Trash view immediately with no y/n — d/D
then Enter sends the marked rows straight to Trash, including pinned rows and batches
that overflow the Trash limit — newest-deleted-first with the deletion age visible,
unless ace.prompt_stash.trash_limit is 0 (see below). In the Trash view, Enter (or
a digit key, or marked Tab/a) restores rows back to Stash while the overlay stays
open; it never loads the prompt bar. d/D stage rows for permanent deletion and
Enter asks for explicit confirmation before purging; Ctrl+Y copies a row. Trash
holds at most ace.prompt_stash.trash_limit entries (default 100; this is an
entry-count limit, not a byte quota): when a discard batch overflows it, the success
toast names the actual evictions and sase prompt stash-archive for recovery. Setting
the limit to 0 disables Trash recovery: Stash d/D plus Enter then delete
permanently (still archived; see below), and a partial discard is applied without a
confirmation prompt. A lowered limit is applied the next time the overlay opens, before
that window is shown. Over-limit rows are permanently deleted, oldest discarded first.
The toast reads Trash limit lowered to N: permanently deleted K oldest draft (or
drafts). The Trash view in the window that just opened still lists the rows and
Trash N/LIMIT count from before that deletion, so the count can be higher than the new
limit. Close the overlay and open it again to see the rows that remain. Enter on a row
that was already deleted does not restore it and does not show a success toast. The
Trash view then repaints from the store, so every already-deleted row disappears. If
applying the limit fails, the stored Trash is left unchanged and the overlay still
opens. The error toast is Failed to reconcile Trash limit: ..., or
Prompt stash is busy — retry when the stash lock times out. Trash recovers only drafts
deliberately discarded from Stash. A successful restore of an unpinned Stash row removes
that row without putting it in Trash. This overlay has no action that deletes History
rows.
A permanent removal, and a replacement of a row's text, frontmatter, or cursor, is
archived first to the append-only prompt_stash_archive.jsonl sitting next to
prompt_stash.jsonl. The copy keeps the full previous text. Reason names are popped,
purged, evicted, and overwritten; see
Recover a Stashed Draft. Moving a row into Trash,
and bringing it back from Trash, do not write an archive copy. A confirmed purge stays
recoverable from that file. List recent archive rows with sase prompt stash-archive (a
bare invocation defaults to list), filter with -q/--query and -r/--reason, inspect
one draft with sase prompt stash-archive show ID, and append drafts back to Stash with
sase prompt stash-archive restore ID... (unique id prefixes work). The purge
confirmation, purge and eviction toasts, and the in-place delete toast all name this
recovery command.
Compact demo — discard fix flaky parser test, then recover it. Leave at least one
other Stash row unmarked. Discarding every Stash row closes the overlay, so the later
steps would start by opening Stash again:
- Stash tab: highlight the row, press
d, thenEnter. The toast readsMoved 1 draft to Trashand the Stash tab's 🗑️ chip shows🗑️ 1. - Press
tto open the Trash view: the row sits newest-first with its deletion age under the🗑️ Trash 1/100pill. - Press
Enter: the toast readsRestored 1 draft to Stash, the overlay stays open, andt/Escreturn to a Stash tab holding the recovered draft.
Trash requires the current stash core binding: restart old TUI processes before the new
behavior takes effect, since mixed-version operation is unsupported. Before the first
new-format write, sase verifies a recoverable pre-upgrade backup of prompt_stash.jsonl
under the stash lock and fails closed when backup creation fails; recover pre-upgrade
drafts from that backup.
Editing an Existing Macro from the TUI¶
Loading a definition into the prompt bar for editing puts the bar into a targeting state instead of a plain draft: the bar tracks the exact source file your edits will write to, and everything below applies whenever the bar shows a target. Every surface that loads an editable definition enters this state the same way:
- The Config Macros child's
Enter(see Editing Macros). - The Select Macro
#picker'sCtrl+O("edit here"), alongside its existingCtrl+E(open in$EDITOR) andCtrl+I(inline-expand) keys. - The jump panel (
Ctrl+],gd) andgdunder the cursor in the prompt bar. - Returning from a whole-bar
$EDITORround trip (Ctrl+G g) preserves whatever target the bar already had — your edits stay bound to the same file.
A read-only source (legacy, plugin, built-in) loads without a target: the bar shows a
persistent read-only marker instead of binding, and gw falls through to the save-as
flow rather than clobbering the original.
Visual state. A targeted bar's border switches from a solid to a double rule so
the state reads at a glance in any theme: $secondary when clean, $warning when dirty
or when the source changed on disk underneath you, and a dim $foreground when
read-only. The border title shows a ✎ <reference> chip using the canonical reference
you'd actually type (#foo, #memory/foo, /skill-name) — not the file stem —
followed by a state marker: dim ✓ (clean), gold ● (dirty), 🔒 read-only, or
⚠ changed on disk (another process edited the file since you loaded it). The
frontmatter panel's border tints to match and auto-shows on a targeted load, even before
you add any frontmatter fields.
Saving. Enter opens the submit chooser by default for non-empty prompt drafts:
ordinary single panes, targeted panes, and multi-pane stacks all use the same panel. The
chooser's rows are target- and pane-count-aware: Enter/s launches a single pane,
Enter/a launches all panes in a multi-pane stack, c launches only the current
pane, w saves to the targeted reference (its subtitle names the destination when
dirty, or says there's nothing to save when clean), and X forks the definition to a
new location. Set ace.prompt_submission.confirm_on_enter: false to make plain Enter
launch the active pane immediately; gw / Ctrl+G w performs the same save directly,
and the bar's subtitle shows a [^G w] save <reference> hint whenever a target is
active. If the source changed on disk since you loaded it, the save opens a conflict
prompt offering overwrite, reload, or save-as instead of silently clobbering the
external edit.
Chezmoi-managed homes. With use_chezmoi: true, editing a definition under $HOME
whose chezmoi source already exists writes to that chezmoi source file, not the
applied copy — the applied copy is regenerated by chezmoi apply and would otherwise
silently discard your edit on the next apply. The border title and the follow-up actions
modal both surface this redirect so it's never a surprise.
Follow-up actions. After a save (or an E / $EDITOR edit), a follow-up modal
offers only the actions that apply to the file you just wrote, each toggleable and run
through the tracked proc queue in order:
- Commit & push — offered when the write path is inside a git repo with changes.
- Apply chezmoi — offered when the write redirected to a chezmoi source; scoped to just the one home target, never a whole-home apply.
sase memory init— offered instead of the two above when you edited a SASE memory note, since it already commits and pushes for you while regeneratingAGENTS.mdand the provider instruction shims.sase skill init— offered instead of the two above when you edited a canonical skill source, since it already commits, pushes, and deploys the generated skill files for you.
With a single offered action, Enter runs it and Esc skips, matching the previous
plain commit/push confirmation. With more than one, each row's key toggles it and
Enter runs everything still selected.
Completion¶
Press Ctrl+T to activate token completion. The completion kind is determined by the
token under the cursor:
- Macro completion: When the cursor is on a
#-prefixed token (e.g.,#my_pro), completion shows matching macro names from all discovery sources, including registered workspace workflow macros. Completion rows include the macro kind and visible typed inputs, with required arguments shown asname: typeand optional arguments shown asname?: typeplus a default when the default is a simple scalar. Standalone workflow references use the#!nameinsertion form; typing#!filters completion to entries whose canonical insertion starts with#!. - Project/Patch completion: When the cursor is on a
+querytoken whose plus is at the start of the prompt or directly after whitespace (including a newline or tab),{, or|, completion opens a project/Patch picker. A plus glued to other text (a+b,c++) and#+queryare not project triggers. The picker contains enabled launchable projects plus active PR-sized Patches inWIP,Draft,Ready, orMailedstatus; system-managedhome, disabled projects, internal sibling backing records, and non-launchable projects are excluded. Typing after the trigger filters by project name, project alias, or Patch name prefix. Project rows show+namein the project's accent color with aprovider · #<workflow>:<name>detail (the current project's row adds acurrentbadge), ordered current project first, then most recently launched, then by name; Patch rows keep their[PR]badge. Accepting a project row places its+<project>tag (or#<workflow>:<name>for names outside tag syntax) at the earliest existing workspace target in that---segment, or at the segment's leading tag position when it has none — and a Patch row places#<workflow>:<patch>the same way. Either way, the other workspace targets in that segment are removed. See Project Tags. - VCS ref completion: When the cursor is inside the root segment of a registered VCS
workflow ref, such as
#gh:,#gh:sa, or#git(, completion lists that provider's projects and active PR-sized Patches. Providers can add namespace rows, such as GitHub organization rows, from local project/config data. Accepting a project or Patch completes only the current ref token, producing#gh:sasein colon form or#gh(sase)in parenthesized form. Accepting a namespace inserts a trailing slash such as#gh:sase-org/and immediately hands off to repository completion. - VCS repository completion: When the cursor is inside a registered VCS workflow ref
that already contains an owner or namespace plus
/, completion lists repositories for that namespace through the owning workspace plugin. For example,#gh:bbugyi200/opens GitHub repositories forbbugyi200, and#gh:bbugyi200/sanarrows locally or through the LSP client's filtering. Accepting a row replaces only the current ref value, producing#gh:bbugyi200/sasein colon form or#gh(bbugyi200/sase)in parenthesized form. Failed or empty lookups show a placeholder row in sase's TUI; stale cached results are reused when a refresh fails. - Slash-skill completion: When the cursor is on a slash-skill token such as
/or/sase_, completion filters the same catalog to skill sources and inserts/<skill_name>— the provider name, not the#reference. The same source completes as#skill/sase_planafter a#, and both forms resolve one definition, so argument hints, previews, and jumps agree. Packaged built-in skills are included, so/sase_plan,/sase_questions, and other bundled SASE skills are available without a project-local skill file. - Macro argument completion: When the cursor is inside a known macro argument
position,
Ctrl+Tcompletes the active argument instead of the macro name. Forpathinputs it delegates to file path completion, forenumandboolinputs it offers the shared Rust choice menu (canonical values with labels, descriptions, and a quietdefaultbadge;true/falsefor bools).modelinputs use the%modelmenu, including aliases after@, provider drill-down, and effort choices after the model's@suffix. Typedmodelinputs open the existing model picker. Inside parenthesized syntax it completes missingname=arguments without repeating names already present in the argument list. Each keyword row shows the shared type label, its default when optional, and its description.#m:and#m(k=open the value menu immediately when the active input has choices or is typedmodel. Accepting aname=row chains straight into its value menu, even from a single-candidate name menu; auto-open shows the choice without accepting it, and empty-prefix menus keep declared order with the first row selected. Accepting a choice replaces the whole value element and inserts the canonical value (quoted when needed), preserving adjacent values and surrounding syntax. At a keyword slot (right after#review(, or after a comma and space in#review(a=1,), the keyword menu also opens automatically while typing (unlessace.prompt_completion.auto_macro_menuis off), and INSERT-modeCtrl+N/Ctrl+Popen it with the first / last keyword highlighted. Accepting a keyword immediately opens its value menu when the input has one (enum/bool values, agent targets, paths, or model values).Enteralways submits the prompt as typed, even on an automatically opened first-row menu;Ctrl+Faccepts the highlighted row without requiringCtrl+N,Down, or another ownership signal. Agent inputs such as#forkoffer agent, proc/monitor, session, clan, and@tribetargets with kind and member context. A proc or monitor row inserts its exact durable proc ID while displaying the friendly, reusable proc name. Session rows also show the associated plan or bead when SASE can resolve one: the row reads<kind> · <phases/waves> · <title>(for exampleEpic · 5 phases · 2 waves · Bead review hardening), and its plan title is searchable, so typing part of the title filters to that session. Selecting the row fills the panel subtitle with more of the same artifact — phase titles for an epic, the goal for a tale or plain plan, the parent title for a phase or task bead. When nothing resolves, the row falls back to a snippet of the session's launch prompt and the subtitle falls back to member names; completion is never blocked either way. Numeric inputs keep the type hint visible but do not invent values. - Frontmatter panel input editing: The frontmatter panel edits
input:rows one cell at a time asname,type,choices,default,description. Inlineenuminputs author their value set in thechoicescell with a flow list such as[wip, draft, {value: ready, label: Ready}]; any other type must leavechoicesempty. Compact local-macro inputs (name:type[=default], ...) keep the existing rich declaration (choice labels and descriptions, input description, repeatability, and role) and only apply the compact default. PressRfor raw-YAML editing, or declare a new inlineenumas a top-level input, when the compact list cannot carry its choices. - Jinja completion: When the cursor is inside a Jinja
{{ }}or{% %}tag, the Jinja menu owns completion ahead of every other surface: variables at expression positions, filters after|, tests afteris, members after<namespace>., and statement keywords (closers for open blocks first) at the head of{% %}. Rows show the name, type or signature, source badge (input,local,sase,jinja,%repeat/%waitfor conditional names,legacy,closes for), and description; the border title names the slot ({{ variables,| filters,is tests,{% statements, or<namespace> members) plus the scope label when the pane is macro-bound. The menu opens automatically while typing inside a tag — right after{{/{%auto-pair, on|and., and on identifier characters — unlessace.prompt_completion.auto_jinja_menuis off; manualCtrl+Tstill works when off. With the menu off, an in-tag cursor still claims completion, so placeholder, directive,@, and#menus leave the tag alone. Next-word ghosts and peeks stay off for the whole tag, whether or not the Jinja menu is open. A|inside a tag always inserts literally as a filter pipe and never triggers%{...}alternation normalization. String literals and new-name positions ({% set x,{% for x) offer no menu, and the open menu suppresses the Jinja diagnostics panel so a momentarily empty{{ }}never flashes red. - Directive completion: When the cursor is on a
%-prefixed directive token (e.g.,%m), completion lists user-facing prompt directives and accepts aliases into their canonical forms. For example,%mcompletes to%modeland%wcompletes to%wait. The same shared directive matrix used by the macro LSP is documented in Directive Completion Matrix:%modelcompletes live model catalog rows, aliases, provider drill-down rows, and parenthesized alias keys;%effort,%auto,%repeat, and%macros_enabledcomplete their fixed values;%id,%clan, and%wait(...)complete their supported keyword names and keyword-value rows.%wait:never offers structured keywords, sotime=andbead=appear only in parenthesized%wait(...).capacity=,priority=/p=, andweight=/w=complete on%queue/%qonly. Keyword completion suppresses duplicates and mutually exclusive keywords, and%model(..., alias=...)suppresses the alias's own@aliasvalue, while manually typed values still flow to launch-time validation. Dynamic agent and bead rows come from sase's TUI warmed snapshots rather than synchronous prompt-bar bead-store reads; if a dynamic refresh is unavailable, static directive names, aliases, keyword rows, and fixed values remain available. - Model shortcuts: When the cursor is on a
=aliasor==modeltoken at the start of a logical line or immediately after a literal ASCII space, completion opens a model shortcut menu.=aliaslists alias rows only; for example, typing=lacan select@largeand rewrite the whole token to%m:@large.==modellists concrete model rows only; for example, typing==gptcan selectgpt-6.1-soland rewrite the token to%m:gpt-6.1-sol, while provider-qualified input such as==codex/gnarrows to that provider. The second equals sign switches an open alias shortcut panel into the explicit model panel. If the token already has a following ASCII space, sase's TUI reuses it and leaves the cursor after that space; before a tab it inserts no extra space; before a newline or prompt end it appends one ASCII space. Typing@directly after a colon-form%m:<model>/%model:<model>value and its single trailing space (at end of line or before whitespace) replaces the space with@and opens the effort-level menu (subject toauto_directive_menu); undo if you meant a literal@reference after the space.Ctrl+FandCtrl+Laccept the highlighted row;Entersubmits the prompt as typed and dismisses the menu instead of expanding the shortcut. No matches dismiss the panel and leave the literal query text intact, so unknown equals tokens submit as ordinary prose. These shortcuts do not fire inside inline code, fenced code, frontmatter, placeholder/directive contexts, escaped equals signs, Markdown-style=text=/==text==marker pairs, or path-like tokens such aspath/=. The old*aliasand**modelforms are ordinary prompt text. The macro LSP uses the same shared filter and edit plans; see Equals model shortcuts.ace.prompt_completion.auto_directive_menuonly controls whether sase's TUI auto-opens these menus and does not govern an external editor's=trigger. @reference completion: A bare@opens the artifact-kind menu before a:appears. Local file rows such as@src/and@Justfilefrom the prompt-selected base directory stay hidden while the typed text prefix-matches an artifact kind; the panel advertises[^T] files, and the firstCtrl+Treveals those rows without accepting or extending the kind. A secondCtrl+Tbehaves as normal completion. The reveal stays active while that menu remains open and the query is narrowed. When no kind prefix-matches, file rows appear automatically, so path-shaped tokens such as@src/naturally show only files. Matching is fuzzy, so a payload is reachable by any memorable fragment of its path or title —@research:sitefinds@research:202607/sase_sites_hub_and_pages/sase_sites_hub_and_pages.md— and@rschfinds theresearchkind. Rows are tiered so a fuzzy hit never outranks a literal one (prefix, then basename prefix, then contiguous substring, then ordered subsequence), then ranked by score, shorter text, and case-insensitive text; see Artifact references for the shared tier table. An empty query is not ranked at all, so opening a menu keeps each group's provider order. After any file reveal,Ctrl+Textends the token to the shared prefix only while every leading row is a literal prefix match; once the query is fuzzy-only there is no shared prefix to insert, so a single remaining row is accepted outright instead. Directory navigation stays exact — only the trailing path segment is fuzzy. Accepting an artifact kind inserts@kind:and immediately opens its payload rows; accepting a directory inserts the@-prefixed directory and drills down; accepting a file inserts the@-prefixed path. Dotfiles are hidden unless the typed path segment starts with.. Documents, explicit artifact files, chats, beads, and agents come from bounded project-scoped catalogs warmed off-thread. Bead rows are loaded from an mtime-cached bead-store snapshot, and agent rows come from a bounded scan of the project's agents sidecar. Agent rows display the readable local name when possible but insert the durable global@agent:<username>.<machine>.<name>spelling. Commit and bug rows appear only from snapshots the mounted Artifacts panes have already loaded, so typing never launches Git, contacts a tracker, or performs unbounded filesystem scans, with one explicit, user-initiated exception: typing a second:right after@<kind>:with an empty payload (the@<kind>::gesture, gated by theref_sync_gestureflag) consumes that keystroke and refreshes the kind's backing sources now — cloning a missing sidecar, force-pulling an existing one past the freshness TTL, or rescanning a local/session-only kind — then reopens the payload menu. A pinned, non-selectable status row shows a live spinner while the sync runs (cloning <repo> …on a first-ever clone,syncing <repo> …otherwise) and settles to<repo> synced · N newor<repo> sync failed · <detail>; rows that arrived from the sync are badged[✦]in place of their usual source badge until the panel closes. A successful sync dismisses its status row after 2.5 seconds; a failed one stays until the panel closes and falls back to a toast if the panel is already gone. The gesture never fires with a non-empty payload (so@file:default:still inserts a literal colon), and repeating it while a sync is already running for that kind is a no-op. Disableref_sync_gestureto fall back to a literal second colon with no sync ever triggered. Payload acceptance replaces the complete@kind:payloadcontext, including when the cursor is in the middle of it. On an un-narrowed bare-@menu,Entersubmits the unexpanded@and dismisses the menu;Ctrl+Faccepts the highlighted row even when it is the untouched first row, andCtrl+Lremains a manual-menu alias. Payload rows are rendered path-first — the source badge, then the reference path with dim directories and a bright basename, then a dimtitle · detail · agetail truncated to the remaining panel width — so what you see is what gets inserted. Matched characters are highlighted in gold wherever they landed, in the path, in the title, in a kind name, or in a local file row, so every row shows why it is there. The panel subtitle reports the same context:~ fuzzywhen any visible row matched below the literal tiers,N of Mfor matching rows out of that kind's known payloads, and a⚠ K not scannedwarning when a catalog cap truncated the candidate set, so a bounded search never reads as an exhaustive one.- Placeholder completion: When the cursor is inside an incomplete
<foobar>tag, completion suggests matching placeholders from the current prompt first, then saved common placeholders learned from tags you have written before. Within the current-prompt group, live tags keep document order and literal-zone tags follow in document order. Current-prompt rows use the cyan<>badge; saved rows use the gold◆badge. sase's TUI retains up toace.prompt_completion.common_placeholder_countsaved placeholders. Automatic completion stays quiet for a bare<and adds saved placeholders only after you type at least one prefix character; manualCtrl+Ton a bare<shows the saved list explicitly. A lone match in the highest-priority group is inserted outright, so saved tags never suppress direct insertion of a lone current-prompt match. Setcommon_placeholder_count: 0to disable saving and display of common placeholders. In the completion panel,Ctrl+Ddeletes the highlighted saved (◆) placeholder from the store; current-prompt (<>) rows are not deletable. By default, submitting from sase's TUI opens Fill in this prompt and asks once for each distinct live tag before launch;Ctrl+Lcan keep a tag literal. Saving a new macro converts the same live tags to typed inputs. Inline-code, fenced-code, and disabled-region tags stay literal in both paths; see Raw Prompt Placeholders.
By default (ace.prompt_completion.placeholder_ranking: smart) saved rows are ranked
by the same weighted composite the history-word menu uses: how strongly a tag relates
to the words and tags already in the prompt (weight 0.50), how recently it was used
(0.30), and how often it has been used (0.20). Each saved row shows the same
5-cell stacked score meter and dominant-reason chip as history words —
⇄ <tag or word> for relation, ◷ <age> for recency, or ✦ <count>× for frequency —
with the current-prompt and saved groups aligned on one shared label column. The
panel's border subtitle adds a matching ⇄ related · ◷ recent · ✦ frequent legend
alongside the <> prompt ◆ saved source legend when both are visible; a narrowing
panel drops the source legend first (the badges are already visible in the rows), then
the signal legend, leaving today's subtitle, and finally just the [^D] delete hint.
The meter and chip degrade the same way on individual rows too narrow to fit them —
the chip is dropped first, then the meter. Set placeholder_ranking: recent to
restore the previous most-recent-use ordering with no signal column, or
placeholder_ranking_signals: false to keep smart ranking but hide the meter, chip,
and legend.
- File path completion: When the cursor is on a path-like token (starting with
/,./,../,~/, or containing/), completion shows matching filesystem entries. Tokens starting with@are also recognized — the@prefix is preserved in the completed path (useful for file-reference arguments). Relative paths use the prompt-selected base directory:+<project>project tags, registered workspace-provider refs, and known-project refs such as#git:<project>or#gh:<owner>/<repo>can root completion in that project checkout. If no prompt workspace ref resolves, sase's TUI uses the TUI process directory. - File-history completion:
Ctrl+G ropens a list of recently referenced files and well-formed@kind:payloadartifact references drawn from prompt history, ranked by recency. Project-local.sase/paths are filtered out so internal bead/plan artifacts don't pollute the suggestions. Artifact references retain their leading@. PressCtrl+Din the completion panel to delete the highlighted entry from the on-disk history. Withace.prompt_completion.next_word: off,Ctrl+Tat a whitespace boundary (or an empty prompt prefix) opens this menu instead, preserving the old slot when there is no next-word chain to request. - Prompt-local word completion: As the first fallback for a plain prose token,
Ctrl+Tfilters words already in the active prompt by the word prefix immediately left of the cursor. Candidates are drawn only from complete words earlier in the prompt, before that prefix; words later in the prompt (including any suffix already sitting to the right of the cursor) are never candidates. Identifier-like candidates may include ASCII hyphens, sobob-mac-captureis matched and replaced as one word; Unicode dash punctuation still acts as a boundary. Matching is case-insensitive and case variants collapse into one row. Insertion honors the casing you started: a prefix with at least two cased characters that are all uppercase uppercases the rest (GITHU→GITHUB); otherwise intrinsic spellings such asGitHub,README, andiPhonestay intact, while a plain word keeps the typed prefix's case and appends its remembered remainder. Accepting a candidate replaces only the typed prefix, so completion works safely in the middle of a word:foo<cursor>bazcompleting tofoobarbecomesfoobar<cursor> baz— a single space is inserted to separate the completed word from a preserved right-hand suffix, and the cursor lands immediately after the completed word, before that space. An exact-prefix spelling is only offered when accepting it would have this separating effect; at a plain word boundary with no suffix to separate, that exact match is suppressed as a no-op. While multiple candidates share a longer common prefix,Ctrl+Tonly narrows the typed prefix and keeps the menu open; it never inserts the separator until a candidate is actually committed. With the menu open, a secondCtrl+Taccepts the highlighted row instead of re-dispatching. Candidates shorter thanace.prompt_completion.word_min_lengthare skipped before history fallback is considered; the default is5, and the threshold applies to the complete candidate rather than the typed prefix. This provider scans only the current prompt pane and takes precedence over history words when it has an eligible match. Candidates are ordered nearest-first: the word you just wrote is the one you are most likely repeating. When the next-word model is warm,word_rankingissmart(the default), andnext_wordis notoff, candidates it predicts for the words before the cursor sort first instead, each marked with a violet⇢ <context>chip naming the evidence context; the border subtitle then carries the matching⇢ contextlegend entry. A cold model, a session-disabled model,word_ranking: recent, ornext_word: offleaves nearest-first order untouched. - History-word completion: When prompt-local words have no match,
Ctrl+Tfilters recently used words derived from recorded prompt history using that same left-of-cursor prefix; any suffix under the cursor is never consulted to include or exclude candidates, only to decide whether accepting inserts the same separating space described above. Hyphenated identifier-like spellings are indexed, matched, length-filtered, and replaced as one word, with only the typed prefix replaced so a preserved suffix survives acceptance. Matching and insertion use the same case-variant collapse and typed-case policy as prompt-local completion. sase's TUI retains up toace.prompt_completion.history_word_countunique words that meet the sharedace.prompt_completion.word_min_length(defaults:10000and5); sethistory_word_count: 0to disable only this final fallback. History is loaded off-thread, so a cold cache briefly showsloading history words…without blocking input. PressingCtrl+Twhile that placeholder row is highlighted re-dispatches (refreshing once the cache is warm) instead of accepting; with real candidates, a secondCtrl+Taccepts the highlighted row.Ctrl+Ddeletes the highlighted word instantly, without rebuilding the index, and records it in~/.sase/prompt_word_deletions.json, so future history derivations continue to filter it out; remove that file to reset all history-word deletions. The formerhistory_word_min_lengthconfiguration key has been replaced byword_min_length.
By default (ace.prompt_completion.word_ranking: smart) rows are ranked by a weighted
composite of three signals rather than plain recency: how strongly a word relates to
the words already in the prompt (weight 0.50), how recently it was used (0.30),
and how often it has been used (0.20). Each row shows a 5-cell stacked meter whose
filled length is the composite score and whose cell colors show each signal's share,
plus a dominant-reason chip — ⇄ <word> for the context word it relates to, ◷ <age>
for recency, or ✦ <count>× for frequency — and the panel's border subtitle carries a
matching ⇄ related · ◷ recent · ✦ frequent color legend. When the next-word model is
warm and word_ranking is smart, rows it predicts for the words before the cursor
sort first with a fourth, violet meter share and a ⇢ <context> chip naming the
sequence evidence, and the legend gains a matching ⇢ context entry.
word_ranking: recent and next_word: off leave that promotion off. The meter and
chip are dropped (leaving the word alone) on panels too narrow to fit them, and the
legend falls back to the plain [^T] accept [^D] delete hint under the same width
pressure. Set word_ranking: recent to restore the previous most-recent-use ordering
with no signal column, or word_ranking_signals: false to keep smart ranking but hide
the meter, chip, and legend.
- Next-word prediction: See Next-word prediction below.
| Key | Action |
|---|---|
Ctrl+T |
Start completion, insert a shared prefix, accept the highlighted word-menu row, reveal a waiting peek, or take one word of a visible ghost or revealed peek |
Ctrl+N / Down |
Next candidate |
Ctrl+P / Up |
Previous candidate |
Ctrl+F |
Accept the highlighted completion-menu row; otherwise act like Right, including accepting an inline ghost when the rest of the line is blank |
Ctrl+L |
Accept the highlighted completion-menu row, or take the whole visible ghost or revealed peek |
Right |
Move right; with only whitespace or nothing after the cursor on this line, take the whole inline ghost |
Alt+F |
Move one word right; with only whitespace or nothing after the cursor on this line, take one inline ghost word |
Enter |
Submit the prompt as typed; a completion candidate stays unaccepted |
Ctrl+D |
Delete a highlighted recent file, saved placeholder, history word, or predicted next word |
Escape |
Cancel completion |
Next-word prediction¶
Confident guesses from your own typed prompt history appear automatically as you type,
with no Ctrl+T needed to see them. They show as dim inline ghost text, or as a border
peek when the guess cannot sit in the line without shifting your prose (mid-sentence
guesses always use the peek, because inline text there would slide the rest of the
sentence right on every keystroke). The border hint is [^T] word [^L] all.
ace.prompt_completion.next_word selects how guesses trigger. auto (the default)
predicts after each typed character: a typed word character completes the word being
typed, and any other typed character asks for the next words. chain shows nothing
while typing and arms only after a prompt-local or history-word commit, or on an
explicit Ctrl+T at the end of a prose word or at a whitespace boundary. off disables
predictions; with off, Ctrl+T at a whitespace boundary opens recent files directly.
With prediction enabled, accepting a prompt-local or history-word completion starts a
next-word chain: each accepted suggestion requests another. An explicit Ctrl+T also
starts a chain at a whitespace boundary with no token under the cursor. At the end of a
prose word, Ctrl+T first tries ordinary current-word completion; it requests next
words only when that completion has no candidate. Structured tokens, such as paths and
Jinja tags, keep their own completion behavior. This also holds in auto: a guess
requested while typing owns Ctrl+T only while its ghost or peek is showing or waiting
to be revealed; otherwise Ctrl+T completes structured tokens, paths, and prompt-local
and history words exactly as in chain mode.
Where a confident guess appears depends on the cursor:
- If the rest of the logical line is blank and the cursor is on its last wrapped row, the guess appears inline. Here, “blank” includes trailing whitespace.
- If only a short closing tail follows, the guess appears inline before that tail, again
only on the last wrapped row. A tail has at most eight non-space characters, all from
)]}"'”’.,;:!?…*_; spaces between them are allowed, and backticks are excluded. - If other text follows, the cursor is on an earlier wrapped row, or even the first
suggested word plus the closing tail will not fit, the guess appears as a border peek.
The peek shows a violet
⇢, its first word in bold, and later words dimmed. - If a letter, digit,
_,',’, or-immediately follows the cursor, no ghost or peek appears. This keeps suggestions quiet when editing inside an existing word.
ace.prompt_completion.next_word_max_words caps suggestions (default 4, clamped to
1–8). An inline ghost drops trailing words until it fits beside any closing tail. A
narrow border drops trailing preview words, then [^L] all, then [^T] word, without
cutting a word. If even ⇢ and the first word do not fit, the peek stays hidden.
Border shortening affects only the display: Ctrl+L can insert later words omitted
from a narrow peek. Use Ctrl+T to accept one word at a time.
A leading space is omitted at the start of the text, after whitespace, and after an opening bracket or quote. A peek is cut before the first suggested word that matches, ignoring case, the first word after the cursor; if its first word already follows the cursor, no peek is shown. A guess based only on a word's overall frequency, without contextual evidence, never reaches the ghost, peek, or next-word menu.
The first explicit request shows a confident guess as a ghost or peek. A weaker guess,
or a confident guess that fits neither surface, can open a next word ⇢ "…" menu naming
the evidence context. Each row shows a word, a violet confidence meter, and a dim
⇢ continuation preview. Accepting a row inserts its word with a separator and
continues the chain. With a ghost or a revealed peek already visible, the next Ctrl+T
accepts one word. Ctrl+L accepts the inline ghost or all words stored in the revealed
peek. Each accept requests another prediction immediately.
Right and Ctrl+F accept the whole inline ghost only when the rest of the line is
blank; Alt+F accepts one ghost word in that same position. Before a closing tail or
with a border peek, these keys move the cursor. An open completion menu takes priority:
Ctrl+F and Ctrl+L accept its highlighted row.
An automatic guess shows inline ghost text immediately. Its border hint waits until text
and cursor have stayed unchanged for 350 ms; an automatic peek stays hidden for that
same interval. An explicit Ctrl+T reveals immediately. While a peek is still waiting,
the first Ctrl+T reveals it and inserts nothing; the next accepts one word.
Typing characters that continue the ghost consumes them. A different character,
Backspace, a cursor move, undo, or redo clears the suggestion. The chain remains usable
only while its text and cursor match the position where it was started. An armed chain
with no candidates shows no next-word guess, or warming next words… while the model
loads. Structural syntax does not open a next-word menu. At a whitespace boundary, a
miss adds [^G r] recent files, pointing to the recent-files menu on Ctrl+G r. With
next_word: off, that whitespace-boundary Ctrl+T opens recent files directly.
Ctrl+D on a highlighted next-word row forgets it through the history-word deletions
store.
In auto, a typed non-word character that follows a word token asks for the next words
— clause punctuation between the word and the trigger is skipped, so a ghost may appear
after , — and, while you type a word with only a boundary after the cursor, the
request completes that word. After a sentence-final ., ?, !, or …, the model
starts a fresh empty context and stays silent. If the keystroke was consumed by a
visible ghost, the ghost is kept and no new request is made.
In auto, current-word completion inserts the untyped suffix plus any continuation. For
example, after typing imple, the ghost might be ment it now. If the word is already
complete, the ghost starts with a space (it now); an exact word with no continuation
stays silent. A current-word peek displays the completed word first. Ctrl+T inserts
its missing suffix, or the next continuation word if no suffix is missing. Ctrl+L
inserts the suffix and continuation. A current-word request that found a guess shows it
as a ghost or peek, and Ctrl+T takes it. When that request shows nothing (including
when the core supplies no current-word completion), Ctrl+T runs ordinary completion
instead of repeating the request. The completed word counts toward
next_word_max_words, leaving at most next_word_max_words - 1 continuation words.
When the text before the cursor exceeds 4000 characters, a typing-triggered
current-word request runs off the keystroke path after
ace.prompt_completion.debounce_ms (default 90 ms). This cutoff does not apply to an
explicit Ctrl+T request or measure text after the cursor.
Ghosts and peeks stay off in NORMAL mode, during file completion, during a snippet
session, while text is selected, and for the whole of a Jinja {{ }} or {% %} tag.
The gate and plan feedback note editor hosts the same autosuggest — ghost, peek, hint,
Ctrl+T, and Ctrl+L — with no completion menu there, so Ctrl+T there still
re-requests a typing-armed guess and shows the hint.
Principles: Ctrl+T never inserts an unseen guess, always moves forward, and stays
silent when unsure.
Press Ctrl+R to open the recursive fuzzy file finder. With a token such as src/alp,
src/ becomes the search root and alp pre-seeds the fuzzy query; with no token, the
finder starts at the prompt-selected base directory described above. If a Ctrl+T file,
recent-file, or path-argument candidate is highlighted, that highlighted path seeds the
recursive root instead. The finder uses
git ls-files --cached --others --exclude-standard from the search root when possible,
falls back to a bounded filesystem walk, and inserts the selected path into the prompt
position captured when the finder opened. Inside the finder, type to filter, use
Ctrl+N / Ctrl+P or arrows to move, Ctrl+U to clear the query, Enter to insert,
and Esc to cancel.
In prompt NORMAL mode, K previews the macro, slash skill, or file under the cursor.
Image files preview inline in the reader, while Ctrl+] still opens images directly in
the artifact viewer. Inside #name: / #name:: argument text, K and Ctrl+]
prefer a nested reference, file path, glossary term, or plain word under the cursor, and
fall back to the macro that owns the argument text only when nothing else matches. On
ordinary prompt text, sase's TUI checks the warm project glossary before falling back to
plain word lookup or spelling fixes. Ctrl+] jumps to a macro, skill, file, or glossary
definition, or opens an action picker when several jump targets are available. Glossary
terms come from the project selected by a leading VCS workflow reference, or from the
active workspace project when the prompt does not select one.
Glossary terms¶
Project glossary entries are authored as strand files under sase/memory/glossary/, one
term per strand file, described by the sase/memory/glossary.md web descriptor; see
Memory Webs. sase's TUI highlights matched glossary phrases in
the prompt after the catalog is warm, rendering them bold, underlined, and in a muted
blue so they read apart from the lavender repo-name highlight — the same "you can
preview this with K or jump to it with Ctrl+]" affordance, a different hue. Matching
skips inline code and fenced code and uses the shared longest-match rules from the macro
LSP. Loading, validation, and matcher compilation run off the render path and are cached
per project/source signature. Strand edits, project changes, and watched memory changes
invalidate the cache.
K on a glossary phrase opens a compact definition card. The title shows the canonical
term and discloses the matched phrase only when you opened an alias. The body renders
the definition as prose, followed by display-alias chips, numbered SEE ALSO chips for
glossary terms mentioned by the definition, and a property grid for project, source, and
match count. Press 1-9 to follow a SEE ALSO term in place and Backspace to walk
back through the card history. y copies the definition, while Y, o, and Z copy
the source path, open the owning strand file's definition line in $EDITOR, or hand the
file to the artifact viewer when a source path is available. Ctrl+] opens the
project-local strand file's definition range through the normal editor/tmux jump flow.
If the catalog is still loading, sase's TUI schedules a warm and asks you to retry
rather than falling through to word lookup or an unrelated jump target.
The card's SEE ALSO chips are the depth-1 case of the same closure resolver behind
sase memory show/read glossary:<keyword> (see Memory Webs):
both walk outgoing reference spans from the shared sase.memory.web.resolution module,
so the preview card and the CLI can never disagree about which terms a definition
references.
Memory panel¶
The Memory panel is the browse-and-edit surface for SASE memory notes, webs, and
strands -- the Markdown files under a content root's sase/memory/ (see
Memory). From a prompt pane, press gm in NORMAL mode or Ctrl+G m in
INSERT or NORMAL. K previews one highlighted glossary phrase in place (see
Glossary terms); gG in NORMAL mode or Ctrl+G G in INSERT or
NORMAL opens this same panel seeded on the glossary strand under the cursor, or on the
glossary web when the cursor is not on a highlighted term. The which-key hint row
lists memory… on both prefixes. If the cursor sits on a #memory/<stem> macro
reference, that note is selected; otherwise the panel opens on the first note. Closing
with Esc or q restores the prompt pane and the vim mode you left.
The header reads MEMORY · <scope> · N notes · scope i/N, always using the configured
PROJECT_NAME: for a project scope, never a ProjectSpec key, plus a right-aligned
⚠ UNPUBLISHED badge once this scope has panel writes that have not been published (see
below).
The note rail is a tree, not a flat list, so the parent edge is visible before you
follow any link: core (type: core) notes sort first by priority then path, then memory
webs (such as glossary) sort by priority then slug, then reference (type: reference)
root notes sort alphabetically, each immediately followed by its children indented one
level under a └ mark. toggle_web (space) expands or collapses a web row in place,
nesting its strands one level deeper the same way note children nest. Each row shows ●
for a core note, ○ for a reference note, or ◆ for a memory web, then ⚙ for a
generated note and ⚠ for a note with an invalid type or parent, then the stem and
a dim description snippet.
Two navigation axes stay synchronized:
- Tree.
j/kmove the note rail cursor; the note card follows.g/Gjump to the first and last note.next_strand/prev_strand(s/S) jump the cursor directly between an expanded web's strands without walking every intervening row./filters notes by stem and description;>extends the match into note bodies.Esccloses the filter and keeps the selection when it is still visible. An empty result readsno notes matched: <pattern>. - Relational. An ordinary note's card carries a numbered
PARENTchip (omitted when the parent isAGENTS.mdrather than another memory note) and numberedCHILDRENchips. A strand's card carries the same chip row labeledSEE ALSO(outbound) andREFERENCED BY(inbound) instead, when its web setslink_reference: implicit— the same mention graphsase memory read's depth-limited resolution walks, plus the same-web[[target]]links the strand bodies author (see Memory Webs and Memory Links). Numbering is continuous across both rows so.1–.9is never ambiguous.Tab/Shift+Tabmove a chip cursor, andlfollows the focused chip -- or chip ① when none is focused..then1–9jumps straight to a numbered chip. Following pushes the previous note onto a trail bounded at 32 entries and clears an active filter when the target is hidden.horBackspacewalks back. A non-empty trail renders asTRAIL a › b › cabove the footer. On the embedded Admin Center Config sub-tab, bare1–9remain top-level Admin Center tab selectors.
follow_link ships as enter,l, but only l currently follows a chip: the note rail
holds focus and consumes Enter for its own selection action. Use l or .1–.9.
The note card also shows a badge row (CORE · always loaded /
REFERENCE · read on demand, plus GENERATED, SHADOWS HOME, ORPHANED, and
INVALID when they apply), the description, the rendered Markdown body, and a property
grid: type, parent, child count, size (lines and approximate tokens), last modified,
last audited read, and the on-disk source path. A web row instead shows WEB plus
EXPANDED/COLLAPSED, and its property grid names strand count and scope -- a web
descriptor declares no type:, so there is no rendering-type row.
The card head is pinned: the title row plus the path line with the pager's state pill
(NOW, uncommitted, UNTRACKED, NO VCS), then a reserved two-row time strip (one
row on short cards). At clean now the second row names what changed last; honest states
use the pager's exact words. ( and ) step the card through committed versions ({
jumps to the first, } returns to now); a violet frame marks the past and Esc returns
to now before closing. = toggles a sticky word-diff view: the shown version against
its parent, the latest change at clean now, or pending edits at dirty now. H opens the
selection in the pager at the card's exact version and view, and C opens the Changes
lens (below). A strand row shows STRAND, plus AUDITED, AUDITING, or AUDIT FAILED
once the panel has recorded (or tried to record) an audited read for it: selecting a
strand's card records an audited read the same way sase memory read <web>:<keyword>
does, so previewing a strand in the panel is itself an attributable access, not a silent
peek.
@ turns the rail into the selected subject's timeline: one row per version in the
pager picker's exact cells, with ▸ on the cursor, ● on the version the card showed
when the lens opened, and ◇ on the compare base. The highlight moves at once while the
card previews through the usual 150 ms debounce (a loading vN… strip covers the
fetch); rail motion pushes no trail entries and never changes the Notes filter. b
marks the cursor row as the compare base (again clears it); the footer reads
Compare vA → vB · b clear, oldest first, or Same version. . reveals hidden
versions (chip shortcuts are inert in the lens) and / filters the version rows. ⏎,
l, and H open the pager at the cursor's pin, view, and base -- a now target can
compare against a committed base. Leaving (Esc, @, or h) restores the Notes rail
exactly (cursor, filter, expansion, trail) while the card stays on the cursor's version,
so a second Esc returns it to now. C is inert inside the lens, and r or a scope
switch leaves the lens first. Clicking the time strip opens the lens.
C turns the rail into a day-grouped Changes review of the current scope (or
All scopes via p/P, which gains that lens-only entry): one row per changeset with
its clock time, first authored subject plus +N more, word delta, and ⌂ for home,
under Today/Yesterday/weekday headers that j/k skip. Regen-only changesets fold
into one count row per day, and the trailing ··· N older row extends the 100-row
window by 100. The card shows the commit subject, ◈ bead / ⬡ agent / ◉ commit
provenance chips, subject totals, and one titled diff section per authored subject (at
most six inline, then +N more), filling progressively with stale loads dropped. ⏎,
l, and H open the first authored subject in the pager diff view at that changeset's
version (.1–.9 open subject N); / filters every fetched changeset by commit
subject, subject names, bead, and agent; r refetches. Leaving (Esc, C, or h)
restores the Notes rail exactly, and @ is inert inside the lens. A scope that fails
(or has no VCS) shows as a header chip instead of being silently dropped.
Every Notes rail row ends with a recency glance: the newest change's class glyph and
compact age (⇧ 8d, ◆ 3h, ⟳ 1h), right-aligned and built off-thread from one
subjects() plus one feed() per scope load, so rows paint first and gain the column
when the map lands (a failed load omits the column and keeps the rail). Promotions
⇧/⇩ keep their highlight; narrow rails shed the age first, then the glyph, and the
stem never wraps. D toggles a trailing DELETED group of subjects whose latest entry
is a deletion (newest deletion first; a deleted-then-recreated subject is not listed):
rows read ✖ name deleted 3w, the header gains · N deleted, and each card is the
tombstone -- the ✖ DELETED pill, a deleted-style frame, the tombstone strip row, and
the last content. Tombstones are read-only ((/{ step older, @ and H work).
A collapsed ▸ INSTRUCTIONS · N group closes the Notes rail, listing every AGENTS.md
instruction file (plus provider shims) with ≡ N shims, ⚠ diverged, and TEMPLATE
chips where they apply. space toggles it; the group header itself selects nothing.
Instruction cards are read-only -- managed files refuse edits
(rendered from memory · edit its source notes), hand-written ones point at o -- and
carry the pager's cause row instead of a meaning row. Stepping ((/)/{/}), diff
(=), the Timeline lens (@), and the pager hand-off (H) all work on instruction
rows exactly as on notes.
The Changes header leads with a review chip from the per-scope watermark: ● N new,
not reviewed yet · m to mark, or ✓ nothing new (omitted when the review state is
unavailable). Rows for changesets newer than the watermark carry a ● dot. m marks
the shown scope reviewed -- optimistic, persisted off-thread -- and toasts
marked N changesets reviewed. The Config hub MEMORY sub-tab badges the same count
(●N), quiet until there is something new.
Memory history reaches the Agents tab too: the SASE CONTEXT / MEMORY lane appends a
version chip to each audited read with a captured blob -- dim ≡ now, vK ⟲ N newer,
amber ◌ uncommitted at read, or one ⟲ N of M changed aggregate for a batch read --
and leads with an AGENTS.md as launched row resolved from launch evidence
(◌ as launched · not in git or snapshot unavailable when it cannot match a commit).
Chips load in a later off-thread pass and append at the row end, so they never delay or
reflow the lane. A v hint on a chipped row opens the pager pinned to the version read;
see Memory History.
p and P cycle the scope ring -- every enabled project with a memory root, the
project you opened from even when it has none (so a can bootstrap it), and one Home
scope resolved through the active use_chezmoi mode,
always ordered by display name with Home last. Precedence for the starting scope is
the prompt's launch-workspace project, then the current project when
it appears in the ring, then the first ring entry. Switching scopes clears the trail and
the filter and restores that scope's last-selected note for the life of the panel.
Ctrl+P opens a filterable scope picker showing each scope's display name and note
count, for reaching a scope with many registered projects without cycling through all of
them.
a and d branch on the selected row. On an ordinary note row, a opens an add form
(stem, type, parent, and description) with live validation; the parent list offers
AGENTS.md plus every reference note in the scope, illegal stems, types, parents, and
parent cycles are rejected before any write, and a successful create offers to open the
new note's body in $EDITOR. e opens the same form pre-filled to retype, reparent, or
redescribe the selected note; the note body itself is not edited in the panel -- press
o to open it in $EDITOR instead. d confirms and deletes the selected note;
deleting a note that still has children is refused with an explanation of which children
must be reparented first, and deleting a short note warns that always-loaded agent
context is being removed. On a web row, a opens an add-strand form (keyword, optional
comma-separated aliases, optional summary, body) with the same live-validation shape,
against the same mutation engine described in Memory Webs. On a
strand row, d confirms and deletes that strand, showing its relative path, keyword,
aliases, the first line of its body, and any inbound Referenced by strands before
anything is written; there is no strand edit form -- press o to open the strand file
in $EDITOR instead. Every delete leaves a timestamped backup and the success toast
names it. A generated note (sase/memory/sase.md and the project-only task_types.md /
sase_artifacts.md / sase_beads.md / sase_sizes.md) renders its GENERATED badge
and refuses edit or delete with an explanation; sase/memory/glossary.md and every
other web descriptor are ordinary user-owned notes, not generated ones. A concurrent
external edit is caught as a conflict: the write is refused, the panel toasts, and the
scope reloads instead of silently overwriting the change.
Every successful write marks its scope UNPUBLISHED, because the write is not visible
to agents until sase memory init regenerates AGENTS.md, the provider shims, and the
memory README. I -- also offered automatically right after a write -- opens a publish
confirmation with a prefilled, editable commit subject and two explicit choices:
Publish & commit runs sase memory init --message "<subject>", and Publish only
runs sase memory init --no-commit. Both run as a captured, non-interactive command in
the scope's content root (Home for the Home scope); a failure surfaces the captured
error and leaves the badge set, while success clears it.
A scope with a memory root but no notes shows a centered invitation naming the scope and
pointing at a. A scope with no memory root yet says sase/memory/ will be created on
the first add. A scope whose read root collided or failed to load shows its diagnostics
and the offending paths instead of a note card.
Passive keys: o opens the note body in $EDITOR and re-checks it for a conflicting
change on return; Z hands the source file to the artifact viewer; y copies the note
body and Y the source path; r re-reads the current scope; ? opens a panel-scoped
help overlay.
The panel footer lists only conditional keys: scope keys when the ring has more than one
scope, link keys when chips exist, back when a trail exists, edit/delete when a writable
note is selected, and publish when the scope is unpublished. Always-available keys live
in ? and in this guide.
Most keys named above are remappable under
ace.keymaps.memory; see
Remapping Memory Panel Keys. Three sets are fixed and
are not part of that scope: Esc and q (close), the . then 1–9 link-chip
shortcuts, and the ↑/↓/Home/End/PageUp/PageDown cursor keys the underlying
list widget supplies alongside the configurable j/k/g/G.
Snippets panel¶
The Snippets panel is the browse-and-edit surface for one project's composed snippet
catalog — the same catalog sase snippet, prompt expansion, and the editor helper use
(see Snippets). From a prompt pane, press gT in NORMAL mode or Ctrl+G T
in INSERT or NORMAL. Lowercase gt / Ctrl+G t / Ctrl+G Ctrl+T still opens the
snippet target pane. The which-key hint row lists snippets… on both prefixes. If the
cursor sits on a #[trigger] call or a bare trigger that is already in the in-memory
catalog, that entry is selected; otherwise the panel opens on the first visible entry.
Closing with Esc or q restores the prompt pane, vim mode, selection, and cursor.
The header reads SNIPPETS · <project> · N snippets · project i/N and always uses the
configured PROJECT_NAME:, never a ProjectSpec key. Generated initial-capital aliases
are metadata on their source entry, not extra rail rows. Macro-derived entries are
viewable and linkable but source-edited: e opens the real macro definition instead of
converting a generated template back into Jinja.
Two navigation axes stay synchronized:
- Alphabetical.
j/kmove the trigger-list cursor; the card follows.g/Gjump to the first and last trigger./filters triggers and source labels;.extends the match into raw and composed bodies. An empty result readsno snippets matched: <pattern>. - Relational. The card keeps the authored template and composed expansion distinct,
highlights
$0/$Ntabstops and#[...]call sites, and carries numberedCALLSchips (outbound) andCALLED BYchips (inbound). Alias calls land on the canonical explicit trigger. Missing targets and cycles stay visible as non-followable diagnostics.Tab/Shift+Tabmove a chip cursor,lfollows, and1–9jump straight to a numbered chip. Following a hidden match clears the filter with a toast.horBackspacewalks a trail bounded at 32 entries.
p and P cycle the enabled-project ring. Order is by display name. Switching projects
clears the trail and the filter and restores that project's last-selected trigger for
the life of the panel.
a opens a trigger/template form with live trigger and link diagnostics, destination
cycling (Ctrl+N / Ctrl+P), a composed preview, and explicit collision wording
(replace vs shadow). e preloads an authored config template and its source
fingerprint; on a macro entry it opens the real source. d confirms a delete naming
backlinks, the exact file being changed, and any lower-priority definition that will
become effective. Writes use the same engine as sase snippet add / delete, run as
tracked procs with one exclusive scope per project and destination, refresh the panel,
publish the change to every mounted prompt immediately (including removing a pending
session overlay on delete), and offer the usual commit/push and scoped chezmoi-apply
follow-ups. A stale-write conflict keeps the draft and offers reload. A failed write
leaves the panel open and unchanged.
A project with no snippets shows a centered empty state. A project whose catalog failed
to load shows the diagnostics. ? opens a panel-scoped help overlay. y copies the raw
template, Y copies the source path, o opens the source in $EDITOR, Z hands the
file to the artifact viewer, and r re-reads the current project.
The panel footer lists only conditional keys: e/d when the selected definition is
writable config, relation keys when chips exist, p/P when the ring has more than one
project, and back when a trail exists. Always-available keys live in ? and in this
guide.
Most keys named above are remappable under
ace.keymaps.snippets; see
Remapping Snippets Panel Keys. Three sets are fixed
and are not part of that scope: Esc and q (close), the 1–9 relation-chip
shortcuts, and the ↑/↓/Home/End/PageUp/PageDown cursor keys the underlying
list widget supplies alongside the configurable j/k/g/G.
Repo names¶
sase's TUI highlights the unambiguous identifier of every non-primary repo in the active
project — linked names (sase-core), sidecar slugs (sase--beads), and external names
(gh:owner/repo) — after the catalog is warm. Matches are bold, underlined, and
lavender. The project's own primary name, sidecar role words (beads, plans), and any
name the project glossary already claims are left as ordinary text so the two overlays
never fight over the same characters.
Matching skips inline and fenced code, ignores path-adjacent hits (../sase-core,
sase-core/crates), and drops the matcher's derived plurals so the highlighted
characters always equal a real identifier. Loading and compilation run off the render
path and are cached per project. Config edits, project changes, and watched sase.yml
changes invalidate the cache. A repo opened with sase repo open during a live sase's
TUI session does not appear until the next config-driven invalidation.
K on a repo mention opens a compact repo card: kind, description, checkout path, clone
coverage, remote URL, and where the repo is declared. The title shows the repo
identifier and discloses the matched text only when it differs in case from the
identifier — the exact-identifier filter above rules out any other difference. Chips
mark the kind, plus AUTO-CLONE and/or AUTO-SYNC when set, and ENV <name> when the
record has one. Checkout prefers the clone registered for the active workspace, else
the record's own path; when that path does not exist locally it is suffixed
(not cloned) and the card prints the exact sase repo open <name> command as a hint
— sase's TUI never runs that command itself. Clones shows
<existing> of <registered> workspaces when the repo has clone records, and rows whose
value is unknown (no remote, no declaration site) are omitted rather than shown empty.
y copies the description, p copies the checkout path, and Y, o, and Z copy the
declaration path, open the owning sase.yml line in $EDITOR, or hand the file to the
artifact viewer — all three warn cleanly for an external repo, which has no declaration
site. If the catalog is still loading, sase's TUI schedules a warm and asks you to retry
rather than falling through to word lookup or an unrelated jump target.
Ctrl+] on a repo mention opens the resolved checkout — the clone registered for the
active workspace, else the record's own path — in $EDITOR or a new tmux pane through
the normal jump action chooser, which gains a c — Open declaration choice whenever the
repo has one (external repos do not). When the checkout is not cloned in the active
workspace, Ctrl+] notifies that the repo is not cloned and prints the exact
sase repo open <name> command to run instead of offering to open a path that does not
exist: it opens the declaration directly when one exists, or just notifies with no
chooser at all for an external repo. sase's TUI never runs sase repo open itself.
Word definitions & spellcheck¶
When no macro, slash skill, workflow, or file target matches, K treats a plain
natural-language word that is not a glossary match as a lookup target. Correctly spelled
words open a scrollable definition panel; use j / k, Ctrl+D / Ctrl+U, and g /
G to navigate it. Misspelled words open a compact correction panel: press 1–9 to
apply a suggestion immediately, or move with j / k and press Enter. The
replacement is an ordinary undoable prompt edit.
Definitions require the optional dict command. Spell checking requires GNU aspell
with an English dictionary (aspell-en on Debian; Homebrew's package bundles English).
If either tool is absent, sase's TUI explains the unavailable feature without affecting
the rest of prompt preview. Run sase doctor -D to see the exact optional-tool status
and installation hint.
Every word K proves misspelled is remembered durably and gets a red underline in every
prompt input from that moment on, in every sase tui session -- no live spell-checking
runs on every keystroke; only what K has already checked is ever squiggled. This is
distinct from the bold blue glossary underline and the bold lavender repo-name
underline, which mark a definable project term or repo rather than a spelling issue. The
correction panel offers two ways to stop fighting a word, at two different scopes. Press
a to accept a word for SASE only: it is recorded in prompt_misspellings.json, K on
it no longer opens the panel, but aspell itself -- and every other consumer of it on
the machine -- still rejects the word. Press d to add the word to your aspell
personal dictionary instead (usually ~/.aspell.en.pws, though aspell configuration
can relocate it), so it stops being flagged everywhere on the machine, not just in
sase's TUI; this is reversible by editing that file directly. The add is verified by
re-checking the word in a fresh aspell process afterwards, so the squiggle clears only
once aspell genuinely accepts it -- a failure leaves the word flagged and reports
aspell's own explanation. Case follows aspell: a word added capitalized (Bugyi)
stays flagged in lowercase (bugyi). Hyphenated words cannot be added with d --
aspell does not permit - inside a personal-dictionary entry -- and the panel reports
that explicitly rather than pretending the add worked. A K press on a
now-correctly-spelled remembered word clears its squiggle automatically. The remembered
words are stored at sase_home()/prompt_misspellings.json; see
ace.prompt_spellcheck to disable the
highlight or change how many words are retained.
sase's TUI also computes a non-disruptive live suggestion after a short debounce while
the prompt input is in INSERT mode. The suggestion appears in the prompt bar subtitle as
[^L] accept ...; press Ctrl+L to accept it. Enter still submits the prompt as
typed, so live suggestions cannot accidentally replace text on send.
Live soft completion covers directives, macro names, macro argument names, and enum/bool
argument values from the same shared choice rows. File-path soft completion is disabled
by default because it can scan the filesystem while typing; enable it with
ace.prompt_completion.auto_file_paths: true. The macro/skill menu also opens
automatically while typing matching #name, #!name, or /skill tokens; disable that
macro auto-open behavior with ace.prompt_completion.auto_macro_menu: false. The
directive menu likewise opens automatically while typing matching % directive tokens,
fixed values such as %model:, and =alias / ==model shortcuts; disable it with
ace.prompt_completion.auto_directive_menu: false. That setting is sase's TUI-only and
does not change macro LSP trigger characters in an external editor. The macro/skill
auto-menu opens only once at least one identifier character follows its marker, so bare
# and / stay quiet. Directive completion opens from a valid bare %, and no
automatic menu ever auto-accepts a single match. The grouped @ reference menu opens
from a bare @, narrowed artifact/file queries such as @pl or @src/, and
syntactically valid @kind: payload contexts; disable automatic opening with
ace.prompt_completion.auto_artifact_menu: false. On an un-narrowed bare-@ menu,
Enter submits the prompt as typed and Ctrl+F accepts the highlighted first row. The
project/Patch picker opens when + completes a token at the start of the prompt or
directly after whitespace, {, or |, and is also available through manual Ctrl+T.
The VCS ref-root menu opens when : or ( completes a known workflow ref trigger such
as #gh: and local candidates exist. The VCS repository menu opens when / completes a
known workflow ref trigger such as #gh:owner/; cached rows appear immediately and
uncached namespaces fetch in a background worker. Placeholder auto-completion opens only
for an incomplete <... context; saved common placeholders join automatic results after
the prefix is non-empty, while manual Ctrl+T can show them from a bare <. Manual
Ctrl+T inserts a lone match in the highest-priority placeholder source group outright;
automatic completion only opens the menu, even for one match. Manual Ctrl+T completion
still supports file paths, macro names, directives, skills, =alias / ==model
shortcuts, @ references, project/Patch tags, VCS ref roots, VCS repository refs,
prompt-local prose words, placeholders, and enabled history words regardless of the
automatic settings. Live suggestions pause while the manual completion panel is open,
while snippet tabstops are active, in NORMAL mode, and during feedback prompts.
For file completion, directories appear before files in the candidate list. Dotfiles are
hidden unless the partial prefix starts with .. Accepting a directory automatically
re-opens completion for the next level (drill-down). The completion panel shows up to
eight candidates at a time — seven when more candidates remain, so the ↓ N more… line
always fits, and one fewer again when the grouped @ reference menu draws its
── files · <base-dir> rule — and scrolls to keep the highlight visible. When exactly
one macro or file candidate matches, accepting completion inserts the canonical
reference immediately.
Accepting a macro completion, or selecting a macro from the #@ picker, opens an
macro args hint panel when the macro has required user-facing inputs. The panel shows
the supported arguments and highlights the active one. Press : while the accepted
reference is still current to switch to colon syntax, or press ( to insert a
required-argument named snippet and use Tab to advance through the snippet fields.
The same smart insertion rules apply to #@ selections and Ctrl+T completions. A
selected macro with no required inputs inserts a trailing space, a single required
non-text input inserts colon syntax, a single required text input inserts double-colon
shorthand, and multiple required inputs insert a parenthesized named-argument snippet.
When that trailing space sits at a live snippet tabstop and the next keystroke is Tab
or Shift+Tab, the jump removes the space on its way to the next tabstop; when the jump
has nowhere to go, the space is kept and ordinary snippet/list fallback continues.
The same hint panel appears while typing narrow, known argument forms such as #name:,
#!name:, #ns/name:, #ns__name:, #name!!:, #name??:, #name(, and
#name(arg=. The hint is advisory; the backend macro parser still owns expansion
semantics when the prompt is submitted. Detection intentionally stays conservative, so
prose shorthand, URLs, unknown macro names, #name+, and completed colon text such as
#name: value do not keep the prompt-bar hint open.
Alt Brace Syntax (%{...})¶
The prompt input has dedicated highlighting and editing help for the %{A | B} alt
fan-out shorthand (see the Alt Directive reference). It
distinguishes the alt delimiters from the branch separators so a fan-out is easy to read
at a glance:
- The
%{opener and}closer are styled as delimiters (bold accent). - Top-level
|branch separators use a dimmed accent so they read differently from the delimiters. - A branch name before a top-level
=(e.g.sec=in%{sec=... | perf=...}) is highlighted as a branch name. - An unmatched
%{(or stray closer) is flagged as an error span.
The alt overlay layers on top of the existing Jinja and search highlighting rather than replacing it, and it uses the same size guards, so highlighting stays responsive on large prompts.
Editing help in sase's TUI prompt input mirrors the Jinja auto-pair behavior and only
fires for the %{...} shorthand. Any % directly before { opens an alternation,
wherever it appears: at a word boundary, mid-word (foo%{bar | baz}qux), after
punctuation, or nested inside another branch:
- Auto-pair — typing
{immediately after%inserts%{ }and parks the cursor between the two padding spaces. The expansion fires at end of line, before whitespace, before a bracket closer (),],},>), and before trailing punctuation (.,,,;,:,!,?), so a fan-out can be inserted before the existing?inWhich is better %{ A | B }?. It remains suppressed before word characters and other token-opening characters. - Paired delete — backspacing the
{in%{|}also removes the auto-inserted}; a forward delete on%|{}removes both braces. |separator normalization — typing|inside a live%{...}span inserts a padded|separator, keeps the cursor after the trailing space and before the closing}, and normalizes comma spacing in the current branch. For example, typing|at the end of%{foo ,bar, and bazyields%{foo, bar, and baz | }with the cursor before}. Openers inside literal zones (inline code, fenced blocks, disabled regions) are ignored; an unclosed span never extends past the cursor's own line, so a stray opener cannot capture a later|; and when alternations nest, the innermost span wins.- No Jinja pair after
%{— typing%,#, or{right after an alternation opener starts a branch (%m:,#macro) instead of a Jinja{% %}or{# #}pair.
These edits are suppressed when there is an active selection or when the cursor is not
inside a %{...} context, so ordinary { and | typing elsewhere is unaffected.
External editor integrations do not own %{} auto-pairing or paired delete;
editor-local brace-pair plugins own that lifecycle there. The
Neovim plugin still provides the same
separator-normalization behavior for prompt buffers.
NORMAL Mode¶
Press Escape or Ctrl+] in INSERT mode to enter vim-style NORMAL mode. Ctrl+] is
equivalent but avoids the terminal escape-sequence ambiguity that can swallow fast
following keys after Escape / Ctrl+[. The border title shows [NORMAL] and line
numbers switch to relative numbering (current line shows absolute, others show offset).
Motions¶
| Key | Action |
|---|---|
h / l |
Move left / right |
j / k |
Move down / up (actual lines) |
w / W |
Next word / WORD start |
e / E |
Next word / WORD end |
b / B |
Previous word / WORD start |
ge / gE |
Previous word / WORD end |
f{c} / F{c} |
Find char forward / backward |
t{c} / T{c} |
Till char forward / backward |
; / , |
Repeat / reverse last f/F/t/T |
% |
Matching bracket |
0 / $ |
Line start / end |
^ |
First non-blank character |
{ / } |
Previous / next paragraph boundary |
gg / G |
Top / bottom of document |
Ctrl+D/Ctrl+U |
Half-page down / up |
All motions accept a numeric count prefix (e.g., 3j moves down 3 lines).
Operators¶
| Key | Action |
|---|---|
d |
Delete (takes a motion, e.g. dw); copies to clipboard |
c |
Change (takes a motion, e.g. cw); cw/cW stop at the word/WORD end; copies to clipboard |
y |
Yank (takes a motion, e.g. yw); copies to clipboard |
> |
Indent lines covered by a motion by two spaces |
< |
Dedent lines covered by a motion by up to two spaces |
gu |
Lowercase text covered by a motion or text object |
gU |
Uppercase text covered by a motion or text object |
g~ |
Toggle case for text covered by a motion or text object |
D |
Delete to end of line |
C |
Change to end of line |
S |
Change entire line |
Y |
Yank from the cursor to end of line (charwise, like y$) |
dd |
Delete entire line |
cc |
Change entire line |
yy |
Yank entire line |
>> |
Indent current line; count indents multiple lines |
<< |
Dedent current line; count dedents multiple lines |
guu |
Lowercase current line; count lowercases multiple lines |
gUU |
Uppercase current line; count uppercases multiple lines |
g~~ |
Toggle case on current line; count toggles multiple lines |
dae |
Delete entire buffer (copies to clipboard) |
cae |
Change entire buffer (copies to clipboard) |
yae |
Yank entire buffer (copies to clipboard) |
Vim-surround commands are also available in NORMAL mode: ys{motion}{delimiter} wraps a
motion or text object, yss{delimiter} wraps the current line, ds{delimiter} removes
the nearest matching surround, and cs{old}{new} replaces it. Quotes and backticks pair
with themselves; either side of (), [], {}, or <> selects the matching pair,
with b aliasing parentheses and B aliasing braces. Other single characters pair with
themselves. For example, ysiw) changes word to (word), and cs)] changes the
parentheses around the cursor to brackets. A count on the ys motion expands its
target; a count before ds or cs selects a farther enclosing pair. Successful edits
are repeatable with ..
Text Objects¶
Text objects compose with d, c, and y.
| Key | Action |
|---|---|
iw / aw |
Inner / a word |
iW / aW |
Inner / a WORD |
i" / a" |
Inner / a double-quoted string |
i' / a' |
Inner / a single-quoted string |
i` / a` |
Inner / a backtick-quoted string |
i(/a(, ib/ab |
Inner / a parenthesized block |
i[ / a[ |
Inner / a square-bracket block |
i{/a{, iB/aB |
Inner / a brace block |
i< / a< |
Inner / an angle-bracket block |
ip / ap |
Inner / a paragraph; ap includes adjacent blank lines |
ae |
Entire buffer |
Operator + Search¶
Any operator followed by / or ? acts up to a search match: d/foo<Enter> deletes
from the cursor up to, but not including, the next foo, and d?foo<Enter> deletes
back to the previous one. It works with d, c, y, gu, gU, g~, >, <, and
ys (the delimiter follows).
| Key | Action |
|---|---|
{op}/{pat}<Enter> |
Operate forward up to the match |
{op}?{pat}<Enter> |
Operate backward back to the match |
{op}n / {op}N |
Operate to the next / previous shared-search match |
2{op}/{pat}, {op}2/{pat} |
Count selects the N-th match (operator × motion) |
While you type, the exact region is tinted in the operator's color: red with
strikethrough for d/c, green for y, and the theme's secondary color for the
transform family. A backward d?foo shows the gold landing match struck through (it
will be deleted), while a forward d/foo leaves it intact. The panel says in words what
Enter will do, such as delete 42 chars · 3 lines, plus the pane-local count (for
example 2/3).
The motion never wraps and stays inside the active pane: d/ only reaches forward and
d? only reaches backward. A match starting exactly at the cursor is skipped. When
there is no target, Enter changes nothing and reports pattern not found,
no match after cursor · 2 before (or no match before cursor · 2 after), or
only 1 match after cursor when a count asks for too many. Esc or Ctrl+C restores
everything.
The motion is exclusive, with Vim's column-0 adjustment: when the range ends at column 0
of a later row, it becomes linewise if the start is at or before the first non-blank
column (for example deleting whole lines), and otherwise stops at the end of the
previous row. Matching is the same smartcase literal matching / uses, and a successful
operator search records the query so n / N continue from it.
. repeats the same operator, query, direction, and count from the current cursor; a
count on . replaces the recorded count, and c replays its inserted text. One u
restores the whole edit. Not supported: regex, /e offsets, empty d/<Enter> reusing
the last pattern (it cancels), counted plain 3/, VISUAL /, d*/d#/gn,
cross-pane operators, and wrapscan for operators.
Other Commands¶
| Key | Action |
|---|---|
i |
Enter INSERT mode; inserted text is repeatable with . |
v |
Enter charwise VISUAL mode |
V |
Enter linewise V-LINE mode |
a |
Append after cursor; inserted text is repeatable with . |
A |
Append at end of line; inserted text is repeatable with . |
I |
Insert at line start; inserted text is repeatable with . |
o |
Open below; prompt bullets and ordered items auto-continue, and inserted text repeats with . |
O |
Open above; prompt bullets and ordered items auto-continue, and inserted text repeats with . |
[<Space> |
Insert blank line(s) above current line without leaving NORMAL mode |
]<Space> |
Insert blank line(s) below current line without leaving NORMAL mode |
u |
Undo |
Ctrl+R |
Redo |
Ctrl+A |
Increment the number at/after cursor, wrapping to the prompt top (supports count and .) |
Ctrl+X |
Decrement the number at/after cursor, wrapping to the prompt top (supports count and .) |
x |
Delete character |
X |
Delete character before cursor |
r{c} |
Replace character(s) at cursor (supports count: 3rx) |
p |
Paste after cursor / below line from the internal register |
P |
Paste before cursor / above line from the internal register |
~ |
Toggle case of character(s) at cursor (supports count: 5~) |
. |
Repeat last mutation, including inserted text; a count replaces the recorded count |
J |
Join current line with next, removing a pulled-up prompt - or <N>. marker (supports count: 5J) |
K |
Preview the macro, workflow, skill, file, glossary term, repo name, or plain word under the cursor |
Ctrl+] |
Jump to the macro/workflow/skill/glossary definition, file, or repo checkout under the cursor |
/ / ? |
Search forward / backward in the current prompt pane; after an operator, acts up to the match |
n / N |
Repeat the last confirmed search in its original / opposite direction |
* / # |
Search forward / backward for the whole word under the cursor |
g* / g# |
Like * / #, but also matches the word as a substring |
In prompt panes, o and O continue the containing hyphen bullet or ordered item below
or above at the same indentation, including when the cursor is on a physical
continuation line produced by Prettier wrapping. Non-list lines retain ordinary bare
open-line behavior. For an ordered item, O on the marker row takes that item's own
number, O on a line the item owns takes the next number (the new marker lands after
that item's marker), and o always takes the next number; the surrounding run is
renumbered either way. Prompt J removes a supported - or <N>. marker when joining
onto a nonblank current line, renumbering the run an ordered item left behind; a blank
current line keeps the marker, and non-prompt editors retain vanilla J behavior.
For Ctrl+], sase's TUI opens the target directly in $EDITOR when there is only one
available action. Inside tmux, or for loadable Markdown macro definitions, it can show a
small chooser for editor, tmux-pane, or load-into-prompt actions. Glossary jumps use the
same flow, targeting the owning project's sase/sase.yml definition scalar.
The border subtitle shows pending operators and counts (e.g., 2d when a delete with
count 2 is pending).
Search previews matching text as you type. Enter confirms the query, while Esc or
Ctrl+C cancels and restores the original cursor. The last confirmed search is shared
by every pane in the current ----separated prompt stack and survives a stack rebuild,
so switching panes and pressing n or N reuses the same query against the newly
active pane.
* and # resolve the keyword run under (or, failing that, forward of) the cursor on
the current line and search for it as a whole word (g* / g# match it as a substring
instead), landing on the start of the destination match. Unlike / and ?, these are
always case-sensitive, matching vim's exemption of * / # from smartcase. n and N
afterward repeat with the same whole-word and case-sensitivity rules, not a plain
smartcase substring search. When no keyword character follows the cursor on the line,
sase's TUI reports "no string under cursor" and leaves the cursor and any existing
search state untouched.
Visual Mode¶
Press v in NORMAL mode for charwise VISUAL mode, or V for linewise V-LINE mode. The
border title shows [VISUAL] or [V-LINE]. Escape returns to NORMAL mode, and o
swaps the active selection end.
Visual mode supports the NORMAL-mode motions and counts listed above, including word
motions, paragraph motions, line motions, f/F/t/T with ;/, repeats, %,
gg/G, Ctrl+D/Ctrl+U, and the NORMAL-mode text objects. v exits charwise VISUAL
mode; V exits V-LINE mode; pressing the other visual key switches selection kind.
Visual changes (d, c, >/<, u/U, ~) are dot-repeatable over a same-sized
range from the current cursor; visual c repeats the replacement text typed before
Escape.
| Key | Action |
|---|---|
d / x |
Delete selection and copy it to the internal register |
c / s |
Change selection and enter INSERT mode |
S{char} |
Surround the exact selection with a delimiter pair |
y |
Yank selection to the internal register and system clipboard |
p |
Replace selection with the internal register |
> / < |
Indent / dedent selected lines by two spaces |
u / U |
Lowercase / uppercase the selection |
~ |
Toggle case in the selection |
* / # |
Search forward / backward for the selected text, literally |
Visual * / # search for the exact selected text (including embedded newlines in a
multi-line or V-LINE selection) rather than a resolved keyword, are always
case-sensitive, and return to NORMAL mode at the search destination.
Visual S uses the same delimiter pairs as NORMAL-mode surround, preserves an exact
charwise selection, and leaves the unnamed register unchanged. In V-LINE mode the
delimiters go inside the neighboring newlines, so the lines outside the selection do not
join the surrounded text. The edit is one undo step and . repeats the saved charwise
length or V-LINE row count from the current cursor; a count before . scales that saved
shape. Escape or Ctrl+X cancels a pending delimiter without replacing the previous
dot-repeat action. Lowercase s keeps its change-selection behavior. V-LINE operators
always apply to whole selected lines regardless of the cursor column.
Prompts Overlay¶
Stash and History live in one Prompts overlay: a single frame with a split-button
Stash tab (≡ Stash N plus a 🗑️ chip) and a ↺ History tab, a consistent list/preview
split, and one contextual footer per view. Trash is the Stash tab's Trash view, opened
with t or the 🗑️ chip; t or Esc there returns to the Stash list, and q closes
the overlay. @ on Stash restores the newest draft, so @@ pops the most recently
stashed prompt when several are stashed. [ and ] cycle the two top-level tabs with
wraparound (even from the focused History filter). From Trash, either bracket opens
History, and coming back restores Trash because the Stash tab remembers its view.
Clicking the Stash label shows the list, while clicking the 🗑️ chip shows Trash. Esc
on the Stash list or on History closes the overlay, and q closes when focus is outside
a text input. Each tab keeps its highlight, scroll, filter, loaded pages, preview
position, and staged marks across switches, the Stash tab remembers its last view, and
History loads lazily on first activation so opening Stash performs no history disk I/O.
Press Ctrl+K from the prompt input to open the overlay on the History tab. That
shortcut is available when the current prompt is a single logical line; that line's
first active workspace reference (e.g. +sase) becomes an initial project:<name>
filter scope once the project-identity snapshot resolves, with the remaining text
preserved as a literal search (see Filtering below). A +<project> tag counts when it
names a known project or leads the line, a # VCS reference always counts, whichever
comes first wins, and references inside code spans are skipped. A blank prompt opens
History with no project filter. Press , then . (written ,.) from a main tab, when
you are not typing in a text field, to open that same unfiltered History tab. ,. reads
the most recently launched workspace prefix other than the built-in #git:home default.
Submitting or editing a row replaces that row's workspace prefix with that recorded
prefix. A +home launch does not create a qualifying prefix. When none is recorded,
sase warns No previously launched VCS macro and opens nothing. ,> opens History with
cancelled prompts visible, and ,Ctrl+G skips the overlay and opens the newest history
entry in $EDITOR. Both use that same prefix check and the same prefix replacement, so
with no qualifying prefix they warn and open nothing. Space on a main tab is separate:
it prefills the prompt input with that same non-home prefix, or opens a blank home
prompt when none is recorded. The History tab loads prompts previously launched from
sase's TUI or sase run in recency pages of ace.page_size rows (default 100). Normal
launch writes skip prompts shorter than five words (e.g. y, ok) so they do not
clutter the list, while failed-launch recovery can still preserve a short submitted
prompt. The same history is available from the shell through sase prompt.
Bare prompts are stored after launch normalization, so a prompt without an explicit
workspace reference appears with the default #git:home prefix. Explicit workspace
prefixes also feed the prompt-input MRU controls. In the prompt input, the MRU ring is
ordered from most recent to oldest: Ctrl+P moves toward older launchable workspace
prefixes, while Ctrl+N moves toward newer prefixes. Each edge has a no-prefix stop
that removes the first launchable workspace tag from the prompt without touching the
remaining prompt text, then wraps. When no workspace tag is present, Ctrl+P starts at
the most recent entry and Ctrl+N starts at the oldest one.
Keybindings¶
| Key | Action |
|---|---|
↑ / ↓ |
Move the highlight (also Ctrl+P / Ctrl+N) |
Enter |
Submit the highlighted prompt directly |
Ctrl+G |
Open the highlighted prompt in $EDITOR |
Tab / Ctrl+I |
Load prompt into the input widget for editing |
Ctrl+J |
Load older prompts (+ace.page_size, default +100) |
Ctrl+K |
Unload the last page, never dropping below the first page |
Ctrl+X |
Toggle visibility of cancelled prompts |
Ctrl+Y |
Copy prompt to clipboard and close the overlay |
[ / ] |
Cycle the Stash tab and History; Trash is remembered |
Esc |
Trash: back to the Stash list. Otherwise: close |
q |
Close the overlay when focus is outside a text input |
Enter submits directly only when the overlay was opened from a History entry point
(Ctrl+K, ,., ,>). When it was opened from a Stash entry point (@, ,@,
Ctrl+G p, or the stash: chip) and you switch to History, nothing launches: Enter
and Tab load the prompt into the prompt bar as a draft, and Ctrl+G opens it in
$EDITOR and then loads the edited text into the bar.
Filtering¶
Type in the search box to filter the prompts that have already been loaded. The grammar
is deliberately small: one optional leading project:<value> qualifier, followed by
an optional literal text substring matched case-insensitively against the prompt's
canonical or humanized text — the plain-substring behavior is unchanged when no
qualifier is typed. project: and its value are case-insensitive; a value ends at
whitespace, or use a double-quoted value (with \"/\\ escapes) to include spaces. A
project: written anywhere other than the leading position is treated as ordinary
search text, not a qualifier. Write \project:... to search for literal text that
starts with project:.
The value resolves against every loaded project's canonical key, configured display
name, and registered aliases (including disabled projects and home) — never by
splitting a displayed basename or guessing from a Patch name prefix. A resolved scope
matches complete project identity (project:sase excludes sase-core and prose that
merely mentions "sase"); for a multi-prompt entry, any segment belonging to the project
is enough. An unknown value still matches a prompt whose own historical reference is
that exact unresolved text, so a deleted or renamed project stays searchable. A label
shared by more than one project is ambiguous and is reported in the helper line below
the filter box instead of matching either one silently; an empty or unterminated quoted
value is also reported there and selects nothing until fixed. The current scope (or the
ambiguous/malformed hint) is shown on that helper line; clearing the project: prefix
returns to searching every loaded prompt.
Press Ctrl+J to load older pages, Ctrl+K to unload the last page, and Ctrl+X to
toggle cancelled prompts on or off — when enabled, cancelled prompts appear in the
results with an x marker. The project: scope only ever considers prompts already
loaded into the overlay; it does not search the whole history archive.
Prompt-history rows are compact single-line entries: cancelled marker, last-used
timestamp (MM-DD HH:MM when parseable), a project column in the project's accent
color, macro/directive chips, and a first-line prompt preview. The preview panel shows
the full prompt, with +<project> tags in their project accent colors, and timestamp
metadata. History writes use a sidecar lock plus atomic tempfile replacement of monthly
shard files under ~/.sase/prompt_history/, so concurrent agent launches do not
truncate prompt history. A legacy ~/.sase/prompt_history.json store is migrated into
shards before normal reads and writes when the shard directory has not already been
created.
Procs Tab¶
Open the SASE Admin Center with #, then press 4 (or switch tabs until you reach
Procs). You can also run the keyless Open procs panel command from the command
palette. The tab shows procs (hook runs, mentor executions, agent launches, plugin
operations, etc.) with live output for running procs and completed output for finished
ones.
Durability and Scope¶
Procs the TUI runs itself are mirrored into the durable proc store
(~/.sase/procs/procs.jsonl, with one combined output log per proc under
~/.sase/procs/logs/), so their outcome survives the session that produced them and is
visible from sase proc list / sase proc show. Supervisor-backed procs — commands
submitted with sase proc run, programmatic submissions, and the unattributed command
fallback for an epic approval whose planner agent session cannot be resolved — are read
back out of that store and rendered here, so work that this process never owned still
shows up on the tab.
The pane defaults to this session plus unattributed procs; press a to widen it to
every session. Historical detached rows remain visible in both modes. The pane title
names the active scope and the running-lane counts, e.g.
Procs · this session ⚙ 2 ⚒ 1 ⚙ 1 [4 running · 5 done]. The blue gear is running
TUI background procs — the same rows the top-bar bg: group counts, excluding monitors,
tool-run carriers, and update rows; the sky-blue ⚒ chip is tool-lane procs (an
inventory count, so it shows a dim ⚒ 0 when empty — it can differ from the top-bar run
count because foreground runs have no proc and a joined run has two); the orange gear is
bare monitors. A green ⚙ N chip appears after the orange chip only while update procs
run, and update rows carry a green ⚙ marker. All counts follow the tab's current
scope, so a moves them with the list. A zero blue/orange lane still renders as a dim
⚙ 0 so a missing chip cannot be read as "unknown". The bracketed totals keep their
current meaning: blue plus tool plus green plus orange equals the running count. Rows
read from the store carry a colored session chip (ace·sase#14 4f2a) that matches the
one sase proc list prints; a session that has since exited renders dim with a †. An
ordinary unattributed proc renders a dim —; a historical detached proc carries a cyan
◆ detached marker that makes the legacy row kind explicit.
Store reads happen on a worker thread and are revalidated by store mtime about once a
second, so the tab never stats, reads, or locks the store from a render or keystroke
path. Retention is governed by procs.history_limit (see
configuration): finished rows and their logs age out
oldest-first, and running procs are never pruned. Because the store owns that retention,
d / D do not dismiss rows; they only explain the retention policy.
The top-bar bg: group's blue chip counts this session's active background procs plus
every active unattributed proc globally, including an approved epic that had to use
the unattributed command fallback. Tool-run carriers are counted in tools: instead,
running bare monitor turns in the orange chip there, and service procs and oneshots are
left to the Services tab.
Layout¶
The tab uses a two-panel layout: a proc list on the left and an output pane on the right. Running procs refresh their output on the pane's 0.25 s tick while the Procs tab is visible.
Proc Status Icons¶
| Icon | Color | Meaning |
|---|---|---|
◌ |
Dim | Pending (supervisor starting) |
● |
Green | Running |
✓ |
Cyan | Success |
✗ |
Red | Error |
⊘ |
Yellow | Killed |
? |
Dim | Unknown |
⚙ |
Orange | Monitor turn (same mark as the Agents tab and the top bar) |
⚒ |
Sky | Tool-run carrier (same mark as the tools: group) |
Monitors on this tab¶
A sase monitor start supervisor is a durable proc like any other, but this tab marks
it the same way the rest of sase's TUI does. See Monitors.
- Orange
⚙. Bare monitor rows carry the orange gear between the status icon and the label (● ⚙ just check-full), matching the Agents tab and the orange chip in the top bar'stools:group. A monitor carrying a live run shows⚒instead. The same mark prefixes the output header. - Agent name. Each monitor names its member agent (
acme--mon) on the list's secondary line (acme--mon · Working...) and on anagentline in the output header. - Status chip. When the matching agent row is loaded, the effective status label
(
TESTINGwhile running,TESTEDonce settled) appears in that pair's accent color between the agent name and the secondary text (acme--mon · TESTING · Working...), and again on the output header'sagentline. - Live
live_reply.md. While the Procs tab is active, the 0.25 s tick refreshes the selected running proc. For a monitor, that output is the tail of<artifacts_dir>/live_reply.md: the last 400 lines, including text rotated intolive_reply.md.1. Until that log has text, the pane still showsWorking.... On the Agents tab, the Reply card follows that same file in place when the live-reply follow is active; see Agents Tab Main Deck. <enter>jumps to the agent. On a monitor whose agent row is loaded,<enter>(or a click) closes Admin Center and reveals that agent on the Agents tab. The hints line shows⏎: agentonly when that jump is possible. If the agent is not on the Agents tab, sase's TUI says so and stays put.- Visible in both scopes. Monitor procs are unattributed, so they appear in both this session and all sessions.
Kstops the supervisor. Kill uses the named-proc stop path: it stops the supervisor, settles the session, and runs any--nextaction.
Durable Procs¶
Procs are durable records shared by every SASE surface, not just rows in this pane. They
live in ~/.sase/procs/procs.jsonl, with one combined stdout/stderr log per proc under
~/.sase/procs/logs/<proc_id>.log. Because the records outlive the process that
produced them, sase proc can list and inspect work started anywhere — including a
TaskTriage launch or unattributed command submitted from another client.
Each proc carries a 12-character id resolvable by unique prefix (three characters
minimum, like a git short SHA), so sase proc show k7m2 works. Statuses are pending,
running, success, error, and killed; terminal states are final, and a proc whose
supervisor died without reporting is reconciled to error rather than left running
forever. sase proc list reuses the icons above and adds ◌ for pending and ⊘ for
killed.
A proc may also carry a named proc: sase proc run -N/--name NAME (bare names
resolve beneath the calling sase-agent; agent/name is fully qualified) names the proc
so sase proc show, sase proc list -N, and sase proc kill can address it by name
instead of id. Resolution tries an exact fully qualified name, then an exact proc id,
then a unique id prefix. Active uniqueness is scoped per project — starting a proc under
a name already held by an active proc in the same project is a conflict — and a name is
only reusable once the proc holding it settles. A monitor's member agent name (for
example acme--mon) is its own named proc.
Kinds and ownership.
| Kind | Typical producer | Owner and scope |
|---|---|---|
tui |
Work run and mirrored by sase's TUI | sase's TUI process; scoped to its session |
command |
sase proc run or sase.procs.submit_proc() |
The proc supervisor; attributed to one session or left unattributed |
detached |
Historical rows from retired CLI/API detached submissions | The legacy proc supervisor; global because no session owns the row |
The programmatic submission API is sase.procs.submit_proc(). Pass session_id=None
when no interactive session should own the row; it still writes a command proc. The
legacy sase.procs.submit_detached_proc() wrapper remains for old callers but now
records the same unattributed command row. The public read_procs() and
filter_procs() helpers accept kind= as either one kind or a collection, including
detached when a caller needs historical rows. A command or historical detached row
that remains pending without a supervisor PID for 60 seconds is reconciled to error;
a mirrored tui row is left to its owning TUI. Epic launches record the approving
surface as ace, telegram, cli, or axe, with api retained as the fallback for
direct or unrecognized API callers.
Session attribution is not delegation: a command proc always executes under its own
supervisor, while its session id decides which TUI includes it by default. --session
accepts a full session id, a unique id prefix or short handle, or current, latest,
and none; the default is this process's sase's TUI session, then the newest live one,
then no session. sase proc run --session none creates an unattributed command row.
sase proc list scopes work to the resolved session plus unattributed rows by default,
and widens to every session with --all. Rows from a session that has since exited
render dim with a † marker.
Retention. procs.history_limit caps how many finished
procs are kept; pending and running work is never pruned for being old. Lowering the
limit removes the oldest finished rows and their log files. Finished procs tagged
command-line (TUI Command Line submissions) keep a separate bucket of 50 beside this
limit. The legacy tasks.history_limit key is still honored as a deprecated alias.
The CLI equivalents are sase proc list, sase proc show ID (--follow to stream),
sase proc run [--session SESSION|none] -- COMMAND (--wait to stream and inherit the
exit code), and sase proc kill ID. A hidden legacy --kind filter remains for
historical kind rows. Approved epics normally launch as monitor turns;
only an unresolvable planner agent session uses an unattributed command proc. See the
CLI reference.
Filtering procs¶
Press / to reveal a query bar above the proc list, prefilled with the query already
active. Typing narrows the list live; the status lane shows N matches or a parse error
with its exact span. Enter commits and returns focus to the list; Esc restores the
query that was active when the bar was opened. Unlike the Artifacts panes, the bar is
hidden when there is no query and appears — read-only, syntax-highlighted — only
while a filter is active, so an active filter is never invisible without also being
silent. The header gains a · N/M shown segment while filtered, and the query survives
closing and reopening the Admin Center in the same session.
Free text (and its explicit text: spelling) matches the command string, the row label,
and the retained output; cmd: and out: narrow to one side. Every key is negatable
with a leading -, and a boolean key takes the bare shorthand (monitor means
monitor:true). The service boolean matches service-proc runs (daemon or oneshot),
and svc:<name> narrows to one service proc by exact name. tag:<tag> matches a proc
tag exactly (for example tag:command-line for Command Line runs, visible by default),
and origin:<origin> matches the submitting origin (for example origin:ace).
The seeded default query is -service, so service-proc runs stay hidden unless asked
for; ace.procs.default_query in sase.yml changes the seed. It applies only when no
committed query is persisted — a user-cleared query stays cleared.
| Key | Kind | Meaning |
|---|---|---|
| free text | string | Command, label, output (implicit AND); same as text: |
cmd: |
string | Command string only |
out: |
string | Retained output only (last 32 KB) |
name: |
string | Row label / display name |
agent: |
string | A monitor row's member agent name |
project: |
string | Project key or display name |
status: |
enum | pending, running, settling, success, error, killed |
kind: |
enum | command, tui, detached |
monitor |
bool | A sase monitor start named proc |
service |
bool | A service proc run (daemon or oneshot) |
svc: |
string | Service name (exact) |
tag: |
string | Proc tag (exact, for example command-line) |
origin: |
string | Submitting origin (exact, for example ace) |
running |
bool | Active and owned by a live session |
failed |
bool | Terminal status is error or killed |
exit: |
int | Exit code (exact) |
min: |
duration | Runtime at least N seconds (or 5m, 2h, 1d) |
max: |
duration | Runtime at most N |
after: |
date | Completed at or after the bound |
before: |
date | Completed at or before the bound |
since: |
date | Started at or after the bound |
until: |
date | Started at or before the bound |
limit: |
host | Row cap; all removes it |
Runtime is (finished_at or now) - started_at, so min:/max: read sensibly for a
still-running proc too. before:/after: bound completion time and since:/
until: bound start time; a running proc has no completion time, so
before:/after: never match it and -before:X includes it. For example,
"just check" -monitor -min:300 matches non-monitor procs that ran for at least five
minutes and mention just check in their command or output.
m cycles the monitor term through three states without opening the bar for editing:
(no monitor term) → monitor → -monitor → (no monitor term)
Each press rewrites the query in place, leaving every other term untouched, and never steals focus from the row list. If the monitor term was the query's last term, the last press clears the filter and removes the bar.
Keybindings¶
| Key | Action |
|---|---|
j / k |
Navigate proc list |
/ |
Filter procs |
m |
Cycle the monitor filter |
' |
Jump to a proc row via adaptive hints |
a |
Toggle scope: this session / all sessions |
K |
Request a kill for a selected active durable proc |
Enter |
Open the selected monitor's agent |
d |
Explain finished-proc retention (does not dismiss) |
D |
Explain finished-proc retention (does not dismiss) |
e |
Open proc output in $EDITOR |
y |
Copy proc output to clipboard |
Ctrl+D / Ctrl+U |
Scroll output pane down / up |
g / G |
Jump output pane to top/bottom |
Tab / Shift+Tab |
Switch Admin Center tabs |
q / Esc |
Close SASE Admin Center |
K and D also answer to Shift+K and Shift+D, matching the pane's existing
Shift+G convention. d and D currently perform no dismissal; both explain that
finished rows age out according to procs.history_limit.
K opens a danger confirmation only for a store-backed row that sase's TUI still
considers active. The backend stops command/named-proc records, including monitor
supervisors. It refuses a TUI-owned record because only its owning sase's TUI session
may stop it, and a legacy record whose supervisor has died is reconciled to error
rather than killed. Other ineligible rows explain why: a finished proc reports
Proc already finished, a proc still being submitted reports
Proc is still submitting — try again in a moment, and a session-local row with no
durable record reports that it cannot be killed from the Procs tab.
Updates Tab¶
Open the SASE Admin Center with #, then press 8. The Updates tab is one
master/detail inventory: every SASE core package, plugin, and registered agent CLI
appears as a row, grouped into SASE, Plugins · Built-in, Plugins ·
Community, and Agent CLIs sections. An always-visible header above the list shows
either the all-current banner or a digest of update counts, cache age, install mode, and
any failed source — it never claims everything is current while a source is unknown or
failed. The list always shows every row (installed or not), with updatable rows first in
each section. ' jumps to any row via adaptive hints, across every section.
Opening the tab never refreshes from the network. The first open in an ACE session
builds the inventory from local caches the automatic update check maintains (plugin
catalog, latest-version caches, editable checkouts' last-fetched upstream refs), without
PyPI/GitHub/npm or git fetch. Later opens reuse the in-memory inventory instantly;
when the automatic check has published newer results, the open re-reads those caches
instead. r refreshes everything from the network, and checked … ago is the age of
the last network check.
Core rows show SASE package versions and incoming commits in their own per-package
detail. Plugin rows bring the full
sase plugin experience:
filter the catalog, inspect a plugin, and install (i), update (U), uninstall (x),
or switch install mode (m). Agent CLI rows are provider-colored, showing installed →
latest versions, exact update or manual commands, vendor docs links, update marks, and
durable update history. Missing agent CLIs that SASE can install show latest v… with
an [npm] or [script] badge and carry the same install verb as plugins: i installs
the highlighted CLI (or every install-marked row), and Space / I marks it for a bulk
install. On any markable row — an installable plugin or CLI, or an updatable CLI — *
marks every visible row in that section with the same action, or unmarks them when all
are already marked. Marked rows show [✓]. Esc first clears every mark (including
filter-hidden ones); with nothing marked, Esc closes the Admin Center. A filter that
matches nothing shows Nothing matches the current filter. Every install opens a
confirm preview first — the exact command, plus for an agent CLI its target directory
and PATH status and, for a script install, the script URL, size, and full SHA-256 — then
runs the previewed plan sequentially in one tracked proc, CLIs before plugins when a
marked set mixes both. CLIs SASE cannot install toast their manual instructions instead.
Providers that opt out of independent CLI management, including the bundled internal
Fakey provider, are omitted from the Agent CLIs section. The plain substring filter
(/) searches every row's own fields — name, owner/repo, description, and topics for
plugins; name, display name, binary, install method, route, package, and not installed
for agent CLIs; package name for SASE rows — across all sections at once.
Every sase-managed agent-CLI update run from ,U, ,E, A, or sase agent-cli update
— and every install run from the Updates tab — is appended to
~/.sase/logs/agent_cli_updates.jsonl. Runs where no command reaches a terminal outcome
are not recorded. Install runs show a green ↓ with installed <version> and carry the
i badge; update runs keep their existing glyphs and badges. Highlighting an Agent CLI
row renders that journal below its details; H toggles between this CLI's executed
install/update rows and a run-grouped timeline across all CLIs. Configure the panel with
ace.updates.agent_cli_history and agent_cli_history_max_rows.
Automatic checks publish one composite snapshot after first paint. Ten-minute session
ticks only revalidate cached SASE/plugin rows and provider names already known outdated;
full discovery waits for the longer configured recompute cadence, and provider registry
lookups retain their own cache. The top bar renders the updates: group as its only
dark chip (lime ⬆ N SASE and sage CLI ⬆ N segments with separate counts, plus a lime
core tag for a sase-core rebuild, plus a green ⚙ gear inset at the left edge while
SASE is updating, a yellow ⚙ gear inset while a tracked restart waits for TUI-local
tasks and installation changes, or a red ⚙ gear inset while the most recent update
attempt failed). Independent commands keep running. Durable operations started from this
TUI still finish their result handling first, within the same 60-second wait.
A red gear means the last update or update-planning attempt settled with an error, or
ACE exited before it finished (an interrupted attempt, whose install may be incomplete).
Clicking the red gear opens the failure report: the failed attempt's label, error, and
last output, with u opening the Update panel, d dismissing the recorded failure, y
copying the report, and q closing. A later attempt that starts after the failure
clears the gear on success, as does dismissing it. The record lives in
~/.sase/update_attempts.json, so it survives ACE restarts and every ACE instance on
the machine converges on it at startup and on the ten-minute update-check tick.
For editable host, core, and plugin checkouts, the running TUI also remembers the Git
HEAD imported by the process and cheaply checks whether the checkout has moved on disk.
It warns once for each new on-disk generation. While any imported root is stale, the
global Update panel (,U) adds Restart ACE as its first row; press x or X to
restart after the same TUI-task and installation-change drain used by post-update
restarts. Independent commands keep running. Durable operations started from this TUI
still finish their result handling first, within the same 60-second wait. This reloads
the already-updated code and does not fetch or modify a checkout.
Every mutation still plans before it runs, and Ctrl+D / Ctrl+U scroll long preview
panes. When commit previews are enabled and a comparable range is available, core and
installed-plugin update confirmations load incoming commits by repository in the
background; install confirmations do not. The global ,U chord opens the Update
panel from already-fetched SASE and provider snapshots — no Admin Center, no live
inventory load. e / s / p (or ⏎ on the highlighted row) choose Everything, SASE,
or providers and then show the same y/n confirmation sase's TUI uses elsewhere,
containing only the selected legs. E / S / P plan those same scopes and skip only
that final confirmation after a runnable preview succeeds; failed or already-current
previews still do not mutate. ,E is the global direct alias for ,U then capital E:
it uses the same cached snapshots, preview planning, no-op handling, and error reporting
without mounting the panel. The providers row lists each captured provider with its
installed-to-latest version transition and marks manual-only providers by name, and the
Everything row summarizes both legs on one line. While a recorded update failure exists,
the panel adds Last update failed (or Last update interrupted) as its first row,
above Restart ACE, with an ✗ failed chip and the failure's label and error; f /
F opens the failure report and d dismisses the failure in place without closing the
panel, and the open panel refreshes when the journal view changes. r re-checks in
place; q / Esc cancel. An Everything confirmation groups SASE and Agent CLI work
into labeled sections with update/current/skipped glyphs, counts, and commands. The
tracked proc runs Agent CLI commands first and the SASE/core/plugin leg second. A
failure in one leg is reported alongside the independent earlier results. After a
changed core/plugin update restarts sase's TUI, the one-shot result toast can show
applied commits grouped by repository as well as file/line statistics. Configure the
toast with ace.updates.post_update_toast_commits, post_update_toast_max_commits, and
post_update_toast_diffstat.
The providers leg still captures the agent-CLI candidates from the latest completed automatic result, revalidates exactly those names, and never broadens the captured set from an Updates-pane load. Manual-only providers remain in the preview with their suggested command or docs. A real SASE/core/plugin code change restarts sase's TUI and its service controller only after provider work finishes, while provider-only updates refresh in place and report per-provider results (versions, failures, and manual commands) in the completion toast instead of restarting. Before that restart, sase's TUI waits up to 60 seconds for TUI-local tasks, in-flight submissions, and installation changes to finish (a toast reports the queued restart) and then restarts anyway with a warning naming whatever is still active. Independent work — agent tool runs, ordinary durable commands, oneshots, monitor turns, and service daemons — keeps running. Durable operations started from this TUI still finish their result handling first, within the same 60-second wait.
u remains pane-wide and updates SASE core plus installed plugins. A is the separate
pane-wide agent-CLI action: it updates marked agent CLIs from anywhere in the pane, and
with no marks it targets every safely updatable installed CLI. i installs
Space/I-marked plugins and agent CLIs together — agent CLIs first, then plugins, in
one tracked proc — or the highlighted row when nothing is marked. See the
Updates tab reference for the full keymap and behavior,
Plugins for the equivalent sase plugin CLI, and
Agent providers for the equivalent
sase agent-cli CLI.
The Updates pane still binds a to a tracked agents-sidecar publication sync. That
action publishes and reconciles agent hoods for every enabled project; it is not part of
the comprehensive update. See Agent Hood Synchronization for
privacy, publication, status, and recovery behavior.
Snippets¶
The prompt input supports expandable text snippets triggered by pressing Tab. Snippets
are configured in the ace.snippets section of sase.yml as a mapping of trigger words
to template strings. Inspect or edit the same catalog from the shell with
sase snippet (list, show, add, delete), or from
sase's TUI with the Snippets panel (gT / Ctrl+G T).
ace:
snippets:
fix: "Please fix the following issue:\n$0"
review: "Review this code for correctness, performance, and style."
bug: "Bug in $1:\n\nExpected: $2\nActual: $3\n\nPlease fix.$0"
Usage¶
- Type a trigger word (e.g.,
fix) in the prompt input. - Press
Tab. If the word before the cursor matches a snippet, it is replaced with the template text. - If the template contains tabstop markers (
$1,$2, ...), the cursor jumps to$1first. PressTabagain to advance to$2, then$3, and so on.Shift+Tabretreats through already visited tabstops.$0marks the final cursor position after all tabstops are visited. If there are no tabstop markers, the cursor moves to the end of the expanded text.
Tab priority: Snippet expansion always takes priority over tabstop advancement. If
you type a trigger word at an active tabstop and press Tab, the snippet expands rather
than jumping to the next tabstop. Expanding inside the live snippet nests the new
snippet session: sase's TUI visits the nested snippet's tabstops first, then resumes the
enclosing snippet at the next outer stop. Expanding outside the current snippet resets
the tabstop session.
Multi-line indentation: When a multi-line snippet is expanded on an indented line, continuation lines automatically inherit the leading whitespace of the trigger line. Tabstop positions are adjusted accordingly.
Trigger words are matched against the alphanumeric/underscore word immediately before
the cursor. If no snippet matches, Tab advances to the next tabstop (if any are
remaining from a previous expansion), and Shift+Tab retreats to a previously visited
tabstop. If neither snippet action succeeds, the key falls back to INSERT-mode list
shifting when the cursor is on a supported marker line. Advancing from the final tabstop
clears the session before that same fallback check runs.
Macro-derived snippets compose normal macro references before they enter the snippet
registry. After macro-derived snippets and ace.snippets are merged, any snippet can
splice another snippet by trigger with #[trigger]. #[trigger(value)] and
#[trigger:value] fill the referenced snippet's $1, $2, ... tabstops before
splicing. The final template is renumbered so tabstops from the caller and referenced
snippets do not collide.
Snippet variables¶
Snippet templates can reference the prompt's target project with the #{project}
variable, which is substituted when the snippet expands on Tab:
ace:
snippets:
epic: "the #{project}-$1 epic bead"
With +sase leading the prompt, epic<Tab> inserts the sase- and leaves the cursor
at $1; with +bob-cli it inserts the bob-cli-. The value is the project's display
name (its PROJECT_NAME, the same prefix bead IDs use), never the directory key.
Resolution order on expansion:
- The prompt's own explicit target: a leading
+<project>tag or#gh:/#git:VCS ref, then a non-home prompt context. - The SASE current project (the TUI's
+<project>chip value). - Otherwise the token stays unresolved.
Only a known variable with a resolved value is replaced. Any other #{name} (for
example Ruby's "#{x}") passes through verbatim, as does an unresolved #{project} --
in that case the TUI shows a warning suggesting a +<project> tag. There is no escape
syntax. Because substitution happens at expansion time, composed callers inherit the
variable for free: a repic snippet built on #[epic] resolves #{project} the same
way. Capitalized aliases uppercase only the template's first character before
substitution, so a substituted project name keeps its own case.
Capitalized aliases¶
Every effective snippet also gains a generated initial-capital alias. For each explicit trigger, SASE uppercases only its first character — the rest of the trigger is preserved byte-for-byte — to form a companion trigger, and uppercases only the first character of the resolved template to form the companion expansion. So authoring only
ace:
snippets:
foo: "foo bar baz"
exposes both foo → foo bar baz and Foo → Foo bar baz.
- Only the first character changes. Triggers that are already capitalized, digit-leading, or underscore-leading produce no extra entry, and a template whose first character has no uppercase form expands unchanged (its distinct trigger alias is still created).
- An explicitly authored capitalized trigger always wins. If both
fooandFooare defined, each keeps its own template, and no alias is generated over the authoredFoo. - Aliases are runtime-only. They are never written back to
sase.yml, macro front matter, or chezmoi source files, and they never prevent you from later defining the capitalized name yourself. - Both spellings participate in
#[trigger]composition, so#[foo]and#[Foo]both resolve, and generated templates preserve tabstop and escape behavior.
The rule applies uniformly to macro-derived snippets, merged ace.snippets, and
snippets saved into the current sase's TUI session — including a second save that
updates an already-pending trigger. The same pairs appear through sase's TUI,
sase editor helper-bridge snippet-catalog, normal LSP completion, and the native Rust
fallback.
You can also create a snippet on the fly from the prompt save panel, opened with gX or
Ctrl+G X. Press Ctrl+X in that panel to switch to snippet mode and choose which
config file should store the new ace.snippets entry. In snippet mode, rows are grouped
by source and sorted alphabetically by trigger; snippet completions elsewhere are listed
in trigger order, too, for stable display. As soon as sase's TUI reports the snippet as
created or saved, it is available to every prompt input already open in the current TUI;
no prompt remount or restart is needed. When
ace.snippet_config_path is configured, this
panel's row list always offers it, pre-selected, as a synthetic destination row — even
when it points outside the standard discovered locations — and shows why if it falls
back to a discovered location instead (for example
configured path unusable: read-only).
When use_chezmoi is enabled, the save panel writes the chezmoi source file first.
sase's TUI keeps that successfully written snippet live as session state even before
deployment. Skipping or failing the optional commit/push/apply step does not remove it
from the running TUI, but another SASE process will not see the source-only change until
chezmoi is applied. SASE applies chezmoi from this flow only after the user confirms the
optional commit-and-push action.
Editors using sase lsp can receive the same registry as LSP snippet completions after
bare trigger words when the client advertises completionItem.snippetSupport. The
server uses the editor helper operation sase editor helper-bridge snippet-catalog as
the authoritative source and falls back to native Rust loading only for simple snippets
if the helper is unavailable. Clients without snippet support do not receive these
entries, because raw $1 / $0 markers would not behave like sase's TUI tabstops.
Save location picker¶
Starting a new mini-macro (gx, Ctrl+G x, Ctrl+G Ctrl+X) or a new snippet (gt,
Ctrl+G t) first shows a location picker: one panel that asks where the new entry
should live. The picker opens synchronously, before any disk reads, so keys typed while
destinations load are buffered and applied — never dropped into the prompt pane.
An Existing section sits at the top of the list, always in the same place: e opens
a fuzzy finder over every physical definition (✎ Edit existing macro… /
✎ Edit existing snippet…). While a pane of that kind is already open the row reads
Switch to existing … instead. The Existing row is never the ★ default. With zero
definitions it stays visible but disabled (no macros yet / no snippets yet).
One keypress picks a destination and Enter accepts the ★ default, whose reason is
shown (★ current, ★ last used, ★ configured, ★ default, ★ override). j/k
(or ↑/↓, Ctrl+N/Ctrl+P) move the highlight and skip headers and unavailable
rows; Esc (or q) cancels and returns focus to the origin pane.
Hotkeys are mnemonic and scope-first: e is existing, p is the project destination,
and h is the home destination in both pickers. In the mini-macro picker the Shift
variant picks the config file of the same scope.
If the prompt pane that opened the picker is gone before you choose — closed, or no
longer in the stack — the picker closes and sase's TUI warns
Prompt pane is no longer available - snippet discarded or
Prompt pane is no longer available - mini-macro discarded. A destination load that
fails stays on the picker and replaces the list with one red error row. For a snippet
that row and the error toast both read Failed to prepare snippet pane: …. For a
mini-macro the row reads Failed to load destinations: … and the error toast reads
Failed to prepare mini-macro pane: …. Cancel still returns to the origin pane when
that pane is still there.
| Picker | Key | Destination |
|---|---|---|
| both | e |
Existing-definition fuzzy finder |
| macro | p |
Project sase/macros/ directory (#<project>/…) |
| macro | P |
Project sase/sase.yml |
| macro | h |
Home ~/sase/macros/ directory |
| macro | H |
User sase.yml |
| macro | 1–9 |
Other Project/Home rows in display order |
| snippet | p |
Project sase/sase.yml |
| snippet | h |
User sase.yml |
| snippet | c |
Configured ace.snippet_config_path (own top section) |
| snippet | 1–9 |
User sase_*.yml overlays in display order |
The default (↵) precedence is:
- Mini-macro:
★ current(the open pane's location when retargeting) →★ last used→★ defaulton the Project directory (the Home directory in home mode) → the first writable row. - Snippet:
★ current(the open pane's location when renaming) →★ configured(an explicitace.snippet_config_path, which outranks last-used) →★ last used→★ defaulton the resolved default file → the first writable row.
The footer previews each destination
(→ <dir>/<name>.md · called as #<ns>/<name> · N macros here for directories,
→ <file> · ace.snippets.<trigger> · N snippets here for snippets). The Existing row
previews
→ fuzzy-find N macros across M files · edit in place or override read-only ones. Rows
that already define the typed name show has #name / has ⇥ trigger. Choosing a
destination locks it for the name step: the old destination cycling is gone, and ⇧Tab
in the name step goes back to the picker while keeping the typed text. Writable plugin
and built-in rows stay collapsed behind one Plugins & built-in summary row (+,
Enter, or a click toggles it), so plain users never see clutter.
Existing-definition finder¶
e opens a two-pane finder: matches on the left, a preview on the right. Type to
filter; ↑/↓/Ctrl+N/Ctrl+P move the highlight while focus stays in the query;
Enter opens the highlighted row; Ctrl+D/Ctrl+U scroll the preview; ⇧Tab returns
to the location picker (the query is remembered for the next e); Esc cancels. Keys
typed as type-ahead after e while the picker is still loading become the finder's
initial query.
Each row is one physical definition. Status chips:
| Chip | Meaning | Enter |
|---|---|---|
● active |
Runtime winner, writable | Edit in place |
◐ shadowed |
Writable, but another definition wins | Edit in place, with a warning |
🔒 built-in / 🔒 plugin / 🔒 read-only / 🔒 from #macro |
Not writable here | Override detour: pick a writable destination, then the name step |
✗ swarm / ✗ workflow / ✗ skill / ✗ memory |
Not a simple mini target | Refused; the verdict explains why |
An in-place edit opens the mini-macro or snippet pane on that definition's body. If a
pane of the same kind is already open, picking a definition replaces the draft (body,
frontmatter, and target) after the usual discard confirmation when the draft is dirty.
Picking the definition the pane already targets just focuses it and reports
Already editing #name. A read-only pick reopens the location picker in override
mode: the Existing row is hidden, destinations that would change the callable name are
disabled, rows that would still lose after save are badged ⚠ shadowed by …, and the
default is badged ★ override. ⇧Tab from that name step returns to the override
picker, not the original one.
Authoring a snippet from the prompt bar¶
gt (NORMAL), Ctrl+G t, or Ctrl+G Ctrl+T (INSERT or NORMAL) opens a dedicated
snippet pane at the bottom of the prompt input stack, a faster loop than the general
save panel above when you already know you're authoring a trigger:
- Choose where.
gtfirst shows the location picker: one keypress picks the config file andEnteraccepts the★default. Presseto fuzzy-find an existing snippet and edit it in place, or start a guided override when the definition is read-only. Keys typed while the destinations load are kept as type-ahead for the next step. - Name it. The trigger-name panel shows the locked destination (
⇧Tabgoes back to change it): type a trigger and it validates live, lists up to six existing triggers that share your typed prefix (Tabcompletes to the highlighted one, and↑/↓orCtrl+P/Ctrl+Nmove that highlight). The verdict line reports one of: an invalid trigger;✓ Createfor a fresh trigger; a warning that the trigger already exists in the destination (Enterwill load it for editing); a warning that it's defined in a different config file (saving here will shadow or be shadowed by that file, per your project's precedence); or a warning that the trigger is derived from a macro and this entry will override it. - Open the pane.
Enteropens the snippet pane — empty for a new trigger, or pre-filled with the current definition (from the destination, the shadowing file, or the derived macro template) when the trigger already exists. The pane always opens in INSERT mode and is unmistakably not a prompt pane: its own separator rule names the⇥ <trigger>and destination, with a state marker (✓clean,●dirty,newfor an unsaved trigger), and its own accent color and subtitle. It is never included in a launch, a stash, or a save-as —Enterin it means "save the snippet", not "submit the stack". - Save it.
Enterin the pane opens the save confirmation, showing[Draft]for a brand-new trigger or opening straight onDiff— a realdifflibunified diff against the existing entry — for an overwrite (Ctrl+Ocycles Draft / Existing / Diff,Ctrl+D/Ctrl+Uscroll). An empty body refuses to save; a byte-identical overwrite reports✓ No changesand closes without writing; a destination that changed on disk since the pane opened warns and offersrto reload the current definition instead of overwriting blindly.Enterwrites the file, publishes the new template to every open prompt input in the session immediately (no restart needed), and closes the pane only once the write succeeds — a failed write leaves the draft in place to retry. - Follow-ups. A successful save runs the same post-write chooser as the general
save panel: an optional commit & push, and — for a chezmoi-managed destination — a
scoped
chezmoi applylimited to the deployed snippet file. - Discard or rename.
Ctrl+Cdiscards the pane; if you've typed anything different from the loaded body, a confirmation guards against losing it.Escreturns to NORMAL mode without discarding.gtagain while the pane is open shows the location picker first (defaulting to the pane's current file), then re-opens the trigger-name panel prefilled with the current trigger to rename or re-target it without touching the body you've written. On close (saved or discarded), focus and the cursor return to exactly the pane and position you were at beforegt.
Macro Picker (#@)¶
Typing #@ (the # character followed by @) opens the Macro snippet picker modal.
This lists all available macros (including project-local macros from sase/sase.yml
files) and inserts the selected reference at the cursor position. Inline-capable macros
and workflows insert as #name; standalone workflows insert as #!name. The picker
uses the same argument-aware skeletons as macro completion, so typed inputs can be
filled immediately after selection. Markdown macro swarms are inline-capable and insert
as #name. This is separate from the ace.snippets mechanism — it provides quick
access to macro references rather than expanding static templates.
Auto-Refresh¶
sase's TUI auto-refreshes data at a configurable interval (default: 10 seconds). The
remaining time until the next refresh is shown in the info panel. Set
--refresh-interval 0 to disable. Press R on Artifacts or Services, or r on Agents,
to open the Refresh panel and choose a manual refresh without waiting
for the next tick.
Tab switches are instant: cached data is shown immediately while a background refresh runs asynchronously, so moving between tabs never blocks on disk I/O.
When the inotify-based artifact watcher is active, the periodic tick is
event-driven: it consults per-surface dirty flags (_dirty_patches,
_dirty_agents, _dirty_axe) and short-circuits the whole tick when nothing has
changed. With ace_refresh_tokens enabled (the default), cheap stat-only change tokens
also skip unchanged surfaces when no watcher is available. A 300-second
--sanity-refresh-interval floor still triggers a full reconcile to recover from missed
events, so a quiet TUI does ~zero work between real changes without going stale.
Performance Tracing¶
For diagnosing TUI latency, set SASE_TUI_TRACE=1 before launching sase tui. Tracing
is near-zero-cost when the env var is unset; with it enabled, each instrumented hot path
emits one JSONL line per span to ~/.sase/perf/tui_trace.jsonl (override via
SASE_TUI_TRACE_PATH=…). See docs/perf_runbook.md for the full
span catalog, benchmark harness, and per-phase performance targets.