Skip to content

Configuration Reference

This document is the central reference for all sase configuration: config files, YAML sections, environment variables, and CLI flags.

Table of Contents

Config File Location

All sase configuration lives under ~/.config/sase/. The base config file is:

~/.config/sase/sase.yml

Overlay files matching the glob ~/.config/sase/sase_*.yml are merged on top of the base file. In the SASE Admin Center new-overlay prompt, enter a single local overlay name rather than a path: extra, sase_extra, and sase_extra.yml all resolve to ~/.config/sase/sase_extra.yml. SASE trims surrounding whitespace and rejects empty names, . / .., or names containing / or \, so the create-overlay flow cannot escape the user config directory. A project-local sase/sase.yml at the detected project root usually takes highest priority. A root-level sase.yml remains an exclusive read fallback during the layout compatibility window; if both files exist, SASE reports a collision instead of merging them. sase's TUI deliberately disables project-local config loading for its own process so opening sase tui inside a repo does not inherit that repo's agent-run settings. See Deep-Merge System below.

Owner Identity

SASE has one explicit owner identity selected for the current machine. Initialize or migrate it interactively with either equivalent command:

sase config init
sase init config

The selected overlay owns both parts of the identity:

id:
  username: alice
  machine_name: athena

id.username is a path-safe, dot-free SASE username. It must be globally unique, should be identical on every machine owned by the same user, and should normally be the user's GitHub username. SASE validates its syntax and reserved names but cannot prove global uniqueness. id.machine_name matches ^[a-z_]+$ and is unique among that user's machines.

The bounded local state file ~/.sase/machine_name (or $SASE_HOME/machine_name) is only a selector. It contains one machine name and is deliberately not portable configuration; it is not the owner identity and cannot supply a missing username. SASE discovers machine overlays by nested id.machine_name first, with deprecated top-level machine_name accepted only as migration input. Foreign machine overlays do not contribute runtime settings, Config inventory layers, or config-defined macros. Ordinary overlays still participate.

Only the selected raw machine overlay can own provenance. An id value in bundled defaults, plugins, ~/.config/sase/sase.yml, ordinary overlays, or project-local config is ignored by runtime merging and cannot change the owner for one project. The selected raw id object remains visible in merged configuration for inspection.

The initializer lists declared machines, suggests a schema-safe hostname when a machine must be chosen, and requires an explicit valid username unless exactly one existing username is clearly confirmed for reuse. It never chooses among conflicting usernames. Creation and migration minimally set id.username and id.machine_name in the same overlay, remove its deprecated top-level key, preserve unrelated YAML/comments, and then write the selector. With use_chezmoi: true, the overlay edit is made in the chezmoi source tree. Direct sase config init uses the normal commit/push/apply deployment; bare sase init combines the edit with deferred chezmoi deployment. The initializer also adds a hostname guard to the chezmoi source .chezmoiignore, staging it in the same commit as the new overlay:

{{ if ne .chezmoi.hostname "<chezmoi-hostname>" }}
.config/sase/sase_<machine>.yml
{{ end }}

The guard uses chezmoi's hostname, which may differ from the SASE machine name, so the overlay is applied only on the machine where it was initialized. If .chezmoiignore already contains an entry for that overlay, the existing guard is left unchanged. The same hostname guards are required when a machine overlay declares memory.h1_title so sase memory init can emit a per-hostname H1 in the chezmoi AGENTS.md.tmpl source.

Prompting requires a TTY. sase config init --check, bare sase init --check, and sase doctor report missing usernames, legacy migration, invalid values, selector mismatches, duplicate overlays, and identity conflicts without writing. Config inspection, help, initialization, doctor, and legacy history remain available while identity is incomplete. Actual agent process creation, new commit provenance, and agents-sidecar mutations require both identity fields and fail with the actionable sase config init instruction.

There is intentionally no bundled identity default.

Machine hoods also provide stable ownership for the hidden agents sidecar. See Agent Hood Synchronization for privacy controls, package contents, import/publication commands, and recovery.

SASE Admin Center (interactive editor)

Press # in the sase tui TUI to open SASE Admin Center. The first press always starts on its lightweight home page, where the working sections—Config, Logs, Machines, Procs, Projects, Statistics, and Updates—are introduced without loading their data. Config's nested catalog is alphabetized. With the default-on admin_center_flags sunset flag it is All, Flags, Holds, Launch, Memory, Snippets, and Macros, labeled 01 through 07. Disabling that flag omits Flags and numbers the remaining six children 01 through 06. While home is visible, press # again to resume the last section that was successfully active in this sase's TUI process. Before the first section visit, the repeated key leaves home unchanged and constructs no pane. Press 1–7 or click the numbered tab strip to enter a section: 1 Config, 2 Logs, 3 Machines, 4 Procs, 5 Projects, 6 Statistics, and 7 Updates. From home, Tab enters Config and Shift+Tab enters Updates; within a working section they wrap across the same tabs. Pane-local [ / ] keys switch sub-tabs or views where the active pane provides them, including Config's nested catalog.

Inside a working section, the same opener 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 rather than an unbounded history stack. A color-coded footer along the bottom of each working section names the jump target (or explains that none exists yet) and is itself clickable.

Each pane is constructed only on first entry and is then reused until the Admin Center closes, preserving filters, selection, and scroll state while avoiding unrelated config, project, machine, log, statistics, proc, update, and macro work on open. Direct commands such as Open logs panel, Open procs panel, Open statistics, and update actions still open their requested pane immediately and make that successfully mounted section the next resume target. Closing and reopening with one # still returns to home; only a second press while home is visible resumes. The top-level resume target and alternate are persisted machine-locally and survive sase's TUI process restarts. Entry bookmarks for Config, Logs, Machines, Projects, Procs, and Updates last only for the current sase's TUI process. They restore by stable identity, along with minimal scope or sub-tab context when needed, but reset when sase's TUI restarts. Filters, marks, scroll positions, loaded data, pane instances, Statistics controls, and other pane-local state are never carried between modal lifetimes.

Config tab

The Config tab answers four questions for every field — what value is effective, why (its provenance), where an edit will go, and whether it validates:

The nested Config catalog is alphabetized. When admin_center_flags is on (the default), it is 01 All, 02 Flags, 03 Holds, 04 Launch, 05 Memory, 06 Snippets, and 07 Macros. With the bundled prefix, press 0 and then 1-7 to open those children. When the flag is off, the catalog runs from 01 All and 02 Holds through 06 Macros, and 0 then 1-6 selects them. Remap the prefix with ace.keymaps.config.select_subtab without changing the visible default badges.

Holds lists the active agent holds with their selectors and expiry. j / k move, d releases the highlighted hold immediately, and r reloads the list. See agent hold limits for the TTL settings.

Flags is a keyboard-first control surface for every code-owned SASE feature flag. It does not edit ~/.config/sase/sase.yml, overlays, project-local sase.yml, or chezmoi source. Enable and disable write a SASE-owned machine-state file under SASE_HOME (normally ~/.sase/feature_flags.json) and then restart sase's TUI and the service host so new processes see the saved value. See feature_flags for precedence, corruption behavior, and the CLI equivalent, and the Config Flags pane for layout, keys, confirmation, and self-disable recovery.

  • Browse / inspect (read-only): a source rail lists each config layer with loaded/missing/invalid/read-only badges; the field tree is generated from the schema (/ filters, : jumps to a dotted path, m shows only modified fields, r refreshes). In the tree, j / k move through visible rows and wrap at the ends, while Down / Up use clamped navigation. ' paints entry hints over the currently visible rows, and a hint key moves the cursor exactly as j / k would, detail panel and selection bookmark included. Hints label nodes in place, so entering and leaving jump mode preserves whatever you collapsed; collapsed children are not hinted and cannot be jumped to. While hints are up they own the keyboard: Esc — or any key that is neither a hint nor the first character of one — leaves jump mode without moving the cursor, so reach for /, m, r, or : after exiting rather than during a jump (in a tree large enough for those letters to be hints, they jump instead). A rebuild of the tree that changes which fields are listed drops the hints and the jump-back history with them. ' stays typable while the filter or path input holds focus. The detail pane shows the type, default, effective value, and the full provenance stack with the winning layer marked. Structured values (object maps and arrays of objects, such as axe.routines or repos) render as a multi-line, syntax-highlighted YAML block instead of a one-line JSON blob, while scalars and short flat lists keep their compact inline form.
  • Edit (↵ or e on a field): a typed editor is generated from the schema — a toggle for booleans, an option cycle for enums, validated inputs for numbers and strings, a line editor for string lists, and a raw-YAML escape hatch for complex shapes. Pick the write scope (ctrl+t cycles user base / overlays / a selected local file; ctrl+n creates a new overlay), or reset a field to its default (ctrl+r, which deletes the key from the chosen scope). A banner states the list-merge consequence (replace vs. append) for the chosen scope.
  • Preview / write (ctrl+s): before anything is written you see the exact per-file text diff, the resulting effective merged value, and schema validation of the candidate config. The write is source-preserving (comments, key order, and quoting are kept) and is remapped to the chezmoi source tree when use_chezmoi is enabled.

For a chezmoi-remapped write, sase's TUI first applies the changed target; an apply failure leaves the source edit in place and keeps the editor open. After a successful write and any targeted apply, sase's TUI checks the file that was actually changed. If that file is dirty inside a git repository, it offers to commit and push the change as a tracked proc. Confirming stages that config file, commits the repository's current index, pulls with rebase, and pushes; pre-existing staged changes are therefore included in the same commit. The repository is discovered from the written file, so a remapped edit uses the chezmoi source repository. When use_chezmoi is enabled, a successful push is followed by a full chezmoi apply. Each failure stops the sequence at that step, without undoing the written config change. Skipping the offer—or editing a file outside git—also leaves the successful write in place. The Launch Control uses the same workflow for persistent alias edits, while its fixed Ctrl+E binding previews and writes llm_provider.default_effort specifically to the user-base layer. Ctrl+E is local to Launch Control modal (including bucket rows), not a configurable leader-key entry. Choosing Provider default writes the empty schema sentinel; a currently active temporary effort override remains effective until expiry or clear.

The deprecated linked_repos and sibling_repos keys remain readable as compatibility aliases for repos.linked, but the Config tab no longer offers a one-key migration action. Prefer editing the config to use repos.linked directly.

SASE Admin Center never writes without showing the diff and validation first, and never edits a built-in or plugin default (those layers are read-only).

Logs tab

The Logs tab lists each log source and a colorized tail of the selected file. After a launch or job failure, sase's TUI toasts a leader chord (,L by default) that opens this tab on that failure's source, highlights the matching header line, and scrolls the detail pane to it. The jump target is session-scoped: it is the most recent error toast in this sase's TUI process, not a durable pointer, and it degrades to the ordinary tail with an in-pane notice if the entry has rotated out of the log.

Machines tab

The Machines tab is the controller's local fleet inventory. It always includes the local controller and adds every enrolled remote alias, with columns for enrollment state, health, capacity, last observation, and endpoint. The local row shows configured runner capacity; every remote row shows not reported because the status response does not include capacity. Opening or reloading the tab reads local inventory only; it never contacts every gateway implicitly. A remote therefore starts with state not checked and health unknown until you press s for one bounded, authenticated hello.

Use / to filter, j / k to move, U to reload inventory, and Enter to close Admin Center and open Agents filtered to machine:<alias> (machine:local for the local row, whose Alias column instead shows the configured machine name). c shows the persistent enrollment flow. For a selected remote, r, R, and x show repair, rename, and removal guidance, while y copies the displayed command; these guidance actions do not mutate machine state by themselves. See the Remote Dispatch Runbook for enrollment and credential handling.

Projects tab

The Projects tab is an inventory and lifecycle surface with three clickable sub-tabs: Projects · Repos · Workspaces. [ / ] cycle those sub-tabs, while Tab / Shift+Tab switch the Admin Center's main tabs.

  • Projects lists true projects—projects backed by their own main ProjectSpec, excluding home and internal linked-repo backing records. Enabled and disabled rows appear together with VCS kind, claim, workspace, repo, and warning counts. a / d enable or disable, i / I initialize the marked or highlighted set or every enabled project, r / w cross-navigate to the selected project's inventories, and the established mark, alias, edit, force, and confirmed-delete actions remain available.
  • Repos lists primary, sidecar, linked, and opened external repos for enabled projects by default. It reports checkout presence, source/config metadata, auto_clone, environment names, and SDD storage mode.
  • Workspaces joins registry entries with active claims, PID liveness, pins, last-used timestamps, TTL staleness, and checkout presence. Missing checkouts point to sase workspace repair.

On Repos and Workspaces, p opens a shared project picker. Choosing a disabled project explicitly reveals its rows; Esc clears the project scope, / text-filters within it, and R refreshes the off-thread cached inventory.

Statistics tab

The Statistics tab aggregates durable agent run and activity records over a selectable time range. Its eight numbered views are 01 Overview, 02 Runners, 03 Projects, 04 Providers, 05 Activity, 06 Macros, 07 Plans & Questions, and 08 Perf. The Runners view uses today's effective global limit—including a temporary override—as present-day context, never as historical configuration. The Projects view can group by project, by Patch, or as a project-to-Patch drilldown. Macros can group by usage, model, project, or co-usage. Perf combines TUI startup and responsiveness logs with telemetry latency and reliability; its grouping cycles through subsystem, provider, and workflow. A pane-wide project filter lets you apply the same scope to the run-backed views, but Perf is global and marks the project chip not applied.

The pane loads only while visible, refreshes every 30 seconds, and performs its queries off the UI thread. Use [ / ] to change views or press 0 followed by 1–8 to select the view displayed as 01 through 08. Use t/T or c to choose a preset or custom range, g to change the Projects, Macros, or Perf grouping, p/P to cycle the project filter forward or backward, and r to refresh immediately. Keyed scope chips keep the effective range, grouping, and project visible; the Group chip appears only in those three groupable views and names the selected dimension there. Project scopes use configured display names while retaining canonical keys internally. First open seeds the current project when ace.current_project.seed_filters is on; p / P can always cycle away from that seed. The cycle order is All projects, followed by projects ranked by run count in the most recently loaded unfiltered result, and then wraps: p moves forward and P backward. Return to All after changing the range to rebuild that list for the new range. If a selected project produces an empty result, either project-cycle key clears directly to All projects. Every populated view includes a compact metric legend, ? opens the complete glossary and current scope, and empty/error states show the effective keys for widening, clearing, or retrying. The Overview Agents Run, Success Rate, and Commits tiles open Projects, while Plans Proposed and Questions open Plans & Questions. The plan and question tiles remain all-project values even when a project is selected; see Telemetry: Admin Center Statistics tab for the view contents, range syntax, and project-filter caveats, and Reading the Admin Center Perf view for Perf data sources and retention.

Updates tab

The Updates tab keeps SASE, its plugins, and its supported agent CLIs current without leaving the TUI, as one master/detail inventory rather than separate tabs per source. Every SASE core package, plugin, and registered agent CLI is a row, grouped into SASE, Plugins · Built-in, Plugins · Community, and Agent CLIs sections. The list always shows every row (installed or not), with updatable rows first in each section.

  • SASE rows show the installed and latest versions of sase and sase-core; the highlighted row's detail includes its incoming commits.
  • Plugins rows bring the full sase plugin experience into the TUI: filter the catalog, inspect a plugin, and install, update, uninstall, or switch install mode.
  • Agent CLIs rows are a provider-colored master/detail browser for Claude Code, Codex CLI, OpenCode, Qwen Code, Antigravity, Muse Code, and Grok Build. Rows show installed → latest versions, install method, ↑ availability, and update marks. Missing CLIs that SASE can install show latest v… with an [npm] or [script] badge (others show [manual]) and install with i (or Space / I marks for a bulk set, mixed with plugin marks in one combined flow). Details show the resolved executable, exact automatic or manual update command, skip reason, canonical vendor docs URL, and the last result; missing CLIs instead show the install route, exact command or script URL, target, and the ↓ i install now · Space mark · * mark all missing call to action. Every install previews first — exact command, target, and PATH status, plus the full SHA-256 for a script — runs without a shell (a script CLI runs the previewed bytes; an npm CLI runs npm install -g <package>), and stays visible afterwards (row, detail, toast, and history), including "installed but not on PATH" with the exact export line. H toggles the agent-CLI run history between the highlighted CLI and every CLI.

The always-visible header above the list shows either the all-current banner or a digest (update counts per source, cache age, install mode, and an offline badge) plus a line per failed source; it never renders "all current" while any enabled source is unknown or failed.

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.

The Plugins rows stay visually consistent with the CLI by reusing the same catalog loader and Rich renderables. They are split into Built-in and Community (third-party, shown with a warning) sections; status glyphs match the CLI exactly: ● installed, ○ available, ↑ update available. Editable / dev installs (both core packages and plugins) carry a lowercase dev marker and are compared against their git upstream instead of PyPI. Update actions route editable packages through the dev-update planner and managed packages through the uv path. Blocked editable states appear as dim reasons such as dev · local changes, dev · diverged, dev · detached HEAD, dev · no upstream, or dev · offline.

sase's TUI computes one composite SASE/plugin/agent-CLI snapshot after first paint. The existing ten-minute session tick only revalidates that cached snapshot and locally probes provider names already present in it. A full inventory/network recompute is eligible on the longer ace.updates.recompute_interval_minutes cadence (one hour by default), while npm latest-version lookups retain their separate six-hour cache. Source failures remain independent, so a provider lookup failure does not erase known SASE/plugin results and vice versa.

The persistent updates: top-bar group is the top bar's only dark chip (a deep moss surface), so it never blends with its neighbors: lime ⬆ N for SASE/plugin updates, with a bright lime core tag when sase-core-rs requires a Rust rebuild, and a sage CLI ⬆ N segment for supported agent CLIs. While SASE is updating, a green ⚙ gear inset leads the badge. Mixed states join the SASE and CLI segments, and the tooltip spells out both counts plus any manual-only CLI updates. Clicking the badge opens this tab without mutating anything, or the Procs tab on the running update while one runs.

The global ,U action opens the Update panel from already-fetched SASE and provider snapshots (no Admin Center, no live inventory load). 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. When the SASE leg needs a sase-core-rs Rust rebuild, the SASE and Everything rows carry the same core tag as the top-bar badge. Lowercase e / s / p (or ⏎ / mouse on the highlighted row) choose Everything, SASE, or providers and still require the final y/n confirmation. Capital E / S / P plan the same scopes and skip only that confirmation after a runnable preview succeeds; failed or already-current previews still do not mutate. Global ,E is equivalent to ,U then capital E: it submits the same Everything preview from cached snapshots and only the runnable preview continues to the tracked update proc. The providers leg still captures provider names from the latest completed automatic snapshot and never adds a newly discovered provider to that invocation. Safe commands run sequentially; Homebrew, non-writable npm, and unknown-provenance installs remain visible with manual guidance. The pane-wide u remains SASE/core/plugins-only, pane-wide A remains the deliberate action for the current agent-CLI inventory, and pane-wide a runs a tracked agents-sidecar publication sync: it publishes and reconciles this machine's own agent hoods for every enabled project and is not part of the comprehensive update. See Agent Hood Synchronization for that command's behavior.

Admin Center mutations preview first, and long confirmation panes scroll with Ctrl+D / Ctrl+U. The Update panel's capital shortcuts are the documented exception: they skip only the final y/n after that same preview succeeds. Plugin and core actions show the exact uv command or editable-checkout plan. When commit previews are enabled and a comparable range is available, confirmations for core and installed-plugin updates load incoming commits by repository in the background; install, uninstall, and mode-switch confirmations do not claim a commit range. An Everything confirmation from ,U groups SASE and Agent CLI work into labeled sections with update/current/ skipped glyphs, counts, and commands (home paths display as ~/). The tracked proc runs Agent CLI commands first and the SASE/core/plugin leg second, reporting independent partial failures. A previews every exact agent-CLI command and every skip with its reason and docs URL; it uses the marked subset from anywhere in the pane, otherwise it targets every safely updatable installed CLI. Agent-CLI commands execute sequentially as one tracked proc and refresh the browser without restarting sase's TUI; new agent launches naturally use the updated binaries. Space (or its alias I) marks any markable row — installable plugins, installable agent CLIs, and updatable agent CLIs — in one shared mark set whose aggregate line counts plugin installs, CLI installs, and CLI updates separately; Esc clears every mark, of any kind and regardless of the active filter, before closing. All slow work runs off the event loop. Core/plugin code changes retain the existing automatic sase's TUI and service host restart behavior after the other legs finish. The context-sensitive keymaps are:

Key Action
j / k Move the highlight down / up
' Jump to a row via adaptive hints, across every section
I / Space Mark / unmark the highlighted row (I is an alias of Space)
* Mark / unmark every visible row in the highlighted row's section with the same action
i Open the install preview for the marked set (plugins and agent CLIs together), or for the highlighted row when unmarked
x Uninstall the highlighted plugin (only when installed)
u Run sase update for SASE core plus all installed plugins
A Update marked agent CLIs from anywhere, or every safely updatable installed agent CLI otherwise
H Toggle the agent-CLI run history between the highlighted CLI and every CLI
a Publish and reconcile every enabled agents repository, draining queued publication retries
U Update the highlighted installed plugin when that row has an update available
m Switch install mode (PyPI managed ↔ dev editable; the sase update --to analog)
r Refresh — refetch the catalog and latest versions (the -r/--refresh analog)
Ctrl+D Scroll the detail panel down
Ctrl+U Scroll the detail panel up
g Scroll the detail panel to the top
G Scroll the detail panel to the bottom
o Toggle offline (cache-only) mode, with a header badge (the -o/--offline analog)
v Toggle verbose list columns — stars / last-updated (the -v/--verbose analog)
/ Focus the filter input (matches every row's own name / description / topics, across all sections)
# (default) From home, resume the last section used; in a section, jump to the previous one, press again to toggle
Tab / Shift+Tab From home enter Config / Updates; otherwise switch SASE Admin Center tabs (1–8 jump directly)
Esc Clear every mark first, including any hidden by the filter; close when no marks are active
q Close SASE Admin Center

The Admin Center opener is the effective ace.keymaps.app.open_config_center binding (number_sign / # by default), so a custom binding is repeated in the same way and appears in the landing-page hint. The remaining section-navigation keymaps above are widget-local and are not configurable through default_config.yml.

Deep-Merge System

Sase builds a merged configuration through five layers, each merged on top of the previous:

  1. default_config.yml — bundled package defaults
  2. Plugin default_config.yml files — from installed plugin packages (via sase_config entry points), sorted by entry-point name; lists concatenate
  3. sase.yml — user config (~/.config/sase/sase.yml); lists replace defaults (not concatenate)
  4. Selected sase_*.yml overlays — ordinary overlays plus only the machine overlay whose nested id.machine_name (or deprecated top-level fallback) matches ~/.sase/machine_name, sorted alphabetically; lists concatenate
  5. Local sase/sase.yml — project-level config at the detected project root (or the legacy root sase.yml fallback); lists concatenate (highest priority)

This allows splitting shared configuration across ordinary files (e.g., sase_work.yml, sase_personal.yml) without duplication and keeping machine-specific settings in selector-safe overlays. Plugins can provide sensible defaults that users can override, and individual projects can customize behavior without changing global config.

Merge semantics:

Type Behavior
Dicts Merged recursively (overlay keys override base keys).
Lists Concatenated in layers 2, 4, and 5; replaced in layer 3 (user config).
Scalars Override (overlay value replaces base value).

For example, given a base file with two mentor profiles and an overlay or local project config that adds a third, the merged result contains all three profiles. A user ~/.config/sase/sase.yml list replaces earlier defaults instead. If two files define the same scalar key (e.g., axe.max_hook_runners), the later layer wins.

The axe block is the one exception to plain dictionary merging: after the layers are combined, SASE recomposes it from the same layer chain so that keyed routine and job entries merge by identity and legacy AXE spellings (see axe) normalize to one effective shape.

Source: src/sase/config/core.py, src/sase/config/loading.py

Configuration Sections

memory.h1_title

Optionally customizes the Markdown H1 title of a generated managed AGENTS.md.

memory:
  h1_title: "Structured Agentic Software Engineering (SASE) - Agent Instructions" # default: null
Field Type Default Description
memory.h1_title string | null null H1 title used by the sase memory init AGENTS.md generator when enabled for a scope.

For ordinary project roots, is_sase_managed: true in that root's own sase/sase.yml is the authorization switch. A managed project with no title derives <project> - Agent Instructions; memory.h1_title alone does not opt a project in. The legacy top-level amd_h1_title key has been removed; it is now reported as an unsupported key by sase config layers instead of being silently ignored.

Home roots are the exception. For the live home root, user config from ~/.config/sase/sase.yml and ~/.config/sase/sase_*.yml can provide the home AGENTS.md title. For the chezmoi home source root, source-side config under dot_config/sase/ is used instead. With use_chezmoi: true, sase memory init initializes the chezmoi home source root rather than writing a live-home AGENTS.md.

When at least one machine overlay (sase_*.yml with a valid id.machine_name) under that chezmoi source config dir declares memory.h1_title, initialization writes AGENTS.md.tmpl and matching *.md.tmpl provider shims whose H1 is a chezmoi hostname switch. Each title-declaring overlay must already have the hostname guard that sase config init writes to .chezmoiignore; missing or duplicate guards block initialization. Machines without their own guarded title keep the last-declared-wins fallback from sase.yml plus overlays. Without any machine-overlay title, chezmoi generation stays static AGENTS.md as before.

Source: src/sase/default_config.yml, src/sase/config/sase.schema.json

generated templates

SASE packages default Jinja templates for managed agent instructions and generated memory Markdown. Managed projects can replace them with root-relative files named in their own sase/sase.yml:

memory:
  agents_template: templates/AGENTS.template.md
  agents_minimal_template: templates/AGENTS.minimal.template.md
  sase_template: templates/memory-sase.template.md
  readme_template: templates/memory-README.template.md
Field Required Jinja variables Generated target or use
memory.agents_template title, core_sections, web_sections, reference_entries Managed root AGENTS.md
memory.agents_minimal_template title, core_sections Create-if-missing minimal AGENTS.md
memory.sase_template project_name, linked_repo_entries Generated sase/memory/sase.md
memory.readme_template memory_notes, total_notes, core_notes, reference_notes, web_descriptor_notes, total_lines, total_tokens Generated sase/memory/README.md

{{ web_sections }} renders the entire ## Memory Webs section, including its own heading, as one numbered H3 subsection per memory web; it is the empty string when a root has no webs, which is what makes the whole section disappear cleanly for a webless root. {{ reference_entries }} renders the entire Reference Memory body: the reference-memory instruction paragraph plus one ordered-list entry per top-level reference note. When a root has no top-level reference notes, {{ reference_entries }} is empty. A custom agents_template must not repeat either section's prose.

The packaged managed template places {{ web_sections }} after the Reference Memory block, so ## Memory Webs renders as the final generated H2 when a root has webs. A custom agents_template controls its own authored layout instead: SASE does not reorder an arbitrary custom template's H2 sections, so a project that keeps its own template in the former Core → Memory Webs → Reference order continues to render in that order.

The legacy top-level amd_agents_template, amd_agents_minimal_template, memory_sase_template, and memory_readme_template keys are deprecated but still read as aliases; when both paths appear in one file, the nested memory.* form wins.

Every configured path must remain inside the project root. Rendering uses strict variables: required placeholders must appear, unknown placeholders are rejected, and the rendered instruction/memory structure is validated before any file is written. Generated agent documents are numbered automatically, so custom AGENTS.template.md headings should not carry their own numbers.

The packaged sase.md template includes the /sase_final terminal-action contract. A custom memory.sase_template is a full-body override and may replace that default prose; the host recovery turn remains the correctness fallback whenever a declaration is actually required.

Home initialization uses convention-based files instead of the project-local path keys. Put AGENTS.template.md, AGENTS.minimal.template.md, memory-sase.template.md, or memory-README.template.md directly in ~/.config/sase/; with use_chezmoi: true, put them in the corresponding source-side dot_config/sase/ directory. See Memory initialization for ownership, preview, and deployment behavior.

Source: src/sase/amd/_template.py, src/sase/main/init_memory/root_rendering.py

is_sase_managed

Controls whether SASE owns repository resources such as project memory, the root AGENTS.md, and explicit SDD initialization.

is_sase_managed: false # default
Field Type Default Description
is_sase_managed boolean false Explicitly authorize SASE to manage resources in the current repository.

Only the target repository's own checked-in sase/sase.yml is consulted for this authorization. A legacy root sase.yml remains readable only when the canonical file is absent. Defaults, user config, and merged overlays cannot opt repositories in globally. When false or absent, memory init does not create, refresh, or validate project memory and does not create or alter the root AGENTS.md; it still propagates every existing project AGENTS.md to provider files beside it. Explicit sase repo init and its sase init repo alias become successful no-ops before provider and storage work. Invalid local YAML or a non-boolean marker fails safely.

This is a direct migration: memory.enabled is retired and does not authorize repository management. Existing managed projects must replace it with top-level is_sase_managed: true.

Home and chezmoi-home memory initialization does not use this project-local switch, and provider instruction copies for existing project AGENTS.md files remain independent of it.

Source: src/sase/default_config.yml, src/sase/config/sase.schema.json

id

Declares the explicit owner in the selected machine overlay:

id:
  username: alice
  machine_name: athena
Field Type Default Description
id.username string none Stable per-user identity shared across that user's machines; syntax and reserved names are validated.
id.machine_name string none Per-user machine name matching ^[a-z_]+$ and the local selector.

The schema permits partial objects so sase config init can diagnose and repair interrupted or legacy migrations, but provenance requires both valid fields.

machine_name (deprecated)

Top-level machine_name remains schema-valid only as read-only migration input for legacy overlays:

machine_name: athena

New writers never emit this form. Run sase config init to move it under id, add the required username, and preserve the rest of the overlay. See Owner Identity for selection, authority, and deployment behavior.

Source: src/sase/config/core.py, src/sase/config/sase.schema.json, src/sase/core/paths.py

ace

Configures sase's TUI behavior. Defaults are provided by src/sase/default_config.yml.

ace:
  axe_description_expanded: true # Services-tab description panel starts expanded
  artifacts:
    description_mode: summary # off | summary | full
    panes:
      "ref:plan":
        description: Project implementation plans
        description_body: Proposed, approved, and archived plan documents.
    relations_expanded: false # Relation panel starts collapsed as a rail; . expands it
    stitches:
      default_query: "sidecar:false merges:hide since:24h"
  tribes:
    default:
      icon: "⌂"
      color: "#87D7FF"
      description: "Agents with no assigned tribe."
    job:
      icon: "†"
      color: "#FFAF5F"
      initially_expanded: false
      description: "Scheduled AXE job automation."
  updates:
    startup_toast: true # show SASE/plugin/agent-CLI updates on startup
    startup_toast_max_commits: 20 # total incoming subjects across repositories
    post_update_toast: true # confirm the version transition after self-update restart
    post_update_toast_diffstat: true # show applied file and line counts
    post_update_toast_commits: true # show applied commits grouped by repository
    post_update_toast_max_commits: 5 # applied subjects shown per repository
    agent_cli_history: true # show Agent CLIs update history in the Updates tab
    agent_cli_history_max_rows: 8 # history rows/runs shown; 0 shows all
    indicator: true # show the segmented SASE + agent-CLI update badge
    prebuild_rust: true # background-cache editable sase-core Rust artifacts
    incoming_commits:
      enabled: true # show incoming commit subjects in the Updates tab
      max_per_repo: 7 # cap subjects per repository
      confirm_max_per_repo: 250 # larger per-repository cap in confirmations
    check_interval_minutes: 10 # attempt a periodic check this often
    check_ttl_minutes: 10 # refresh latest-version checks at most this often
    recompute_interval_minutes: 60 # periodic full network recompute cadence
  prompt_submission:
    confirm_on_enter: true # plain Enter opens the prompt submission panel first
  keymaps:
    config:
      select_subtab: "0"
    statistics:
      prev_view: "left_square_bracket" # active only while Statistics is focused
      next_view: "right_square_bracket"
      select_view: "0"
      jump_to_entry: "apostrophe"
      cycle_range: "t"
      cycle_range_reverse: "T"
      custom_range: "c"
      cycle_group: "g"
      cycle_project_filter: "p"
      cycle_project_filter_reverse: "P"
      scroll_down: "ctrl+d"
      scroll_up: "ctrl+u"
      refresh: "r"
      help: "question_mark"
    app:
      next_patch: "j"
      prev_patch: "k"
      edit_query: "slash" # structured query on Artifacts and Agents; defaults render as `/`
      show_help: "question_mark" # app-level Help; defaults render as bare `?`
      # ... all app-level keybindings are configurable
    modes:
      # Built-in modes (fold, copy, leader, bang) are configurable
      leader_mode:
        prefix: "comma"
        keys:
          repeat_last: "comma" # press the leader prefix, then this key; defaults render as `,,`
          search_forward: "slash" # Agents metadata search; defaults render as `,/`
          models_panel: "m"
          update_sase: "U"
          update_everything: "E"
          full_history_refresh: "y" # with refresh_panel on, opens the Refresh panel
          collapse_fold_by_hint: "H" # Agents fold-collapse hints; `,H`
      fold_mode:
        prefix: "z"
        keys:
          set_level_1: "1" # PR detail: set every section to level 1
          set_level_2: "2"
          set_level_3: "3"
          cycle_stitches: "c" # `cycle_commits` is still accepted as a legacy alias
          cycle_hooks: "h"
          agents:
            set_level_1: "1" # session 1-2; clan/agent 1-3; tribe 1-4
            set_level_2: "2"
            set_level_3: "3"
            set_level_4: "4"
      # Custom modes can be added here
      my_mode:
        prefix: "B"
        keys:
          run_tests:
            key: "t"
            shell: "just test"
Field Type Default Description
artifact_file_viewer dict see below mpv-backed terminal video playback settings for the external artifact-file viewer.
artifacts dict see below Per-pane settings for sase's TUI Artifacts tab.
axe_description_expanded bool true State the Services-tab description panel for service procs, routines, and jobs starts each session in; d toggles it in memory.
current_project dict see below Top-bar +<project> chip and session seeds for project filters.
keymaps dict - Configurable keybindings (see below).
notification_indicator_max_counts int 4 Per-tab counts shown in the top-bar notification indicator before the rest collapse into +N.
notification_rules list [] Ordered rules for whether a notification toasts and what sound it plays; see Delivery Rules.
notification_tabs dict see below Per-tab colors, icons, priorities, and grouping for notification tabs.
page_size int 100 Ctrl+J / Ctrl+K step and the default Artifacts limit: value. Must be at least 1. Launch Control alias history uses a fixed 10-run step instead.
prompt_completion dict see below Live soft-completion settings for sase's TUI prompt input.
prompt_inputs dict see below Prompt input collection settings for raw <placeholder> tags and macro-save conversion.
prompt_spellcheck dict see below Sticky misspelling highlight settings for sase's TUI prompt input.
prompt_stash dict see below Stash Trash recovery settings for sase's TUI Prompts overlay.
agent_decks dict see below Agent data deck spread versus paged rendering settings for the Agents tab.
agent_header dict see below Collapsed Agents-tab header panel MACRO preview budget.
agent_tabs dict see below Agent tab machine mode and per-tab styling for the Agents tab.
prompt_submission dict see below Plain-Enter submission confirmation settings for sase's TUI prompt input.
repro_output_dir str "" Base directory for Agents-tab reproduction bundles. Empty means <SASE_HOME>/repros (default ~/.sase/repros).
snippet_config_path str "" Config file that receives new ace.snippets entries written from the prompt bar (see below).
snippets dict[string] {} Trigger-word → template mappings for prompt input snippet expansion.
tool_calls dict see below Tool-call presentation settings for sase's TUI Agents tab.
tribes dict see below Per-tribe sase's TUI icons and identity colors, plus Agents-tab panel initial expansion.
updates dict see below Startup update checks, the top-bar update badge, and the one-shot post-update restart confirmation toast.

ace.artifact_file_viewer

Controls terminal video playback when the external artifact-file viewer opens a video artifact through mpv.

ace:
  artifact_file_viewer:
    video:
      audio: false
      loop: false
      vo: "kitty"
      extra_mpv_args: []
Field Type Default Description
video.audio bool false Start playback unmuted.
video.loop bool false Loop playback with mpv --loop-file=inf.
video.vo str "kitty" mpv video output driver, such as kitty or tct.
video.extra_mpv_args list[str] or str [] Extra mpv arguments appended after SASE's own playback options.

SASE launches mpv with --no-config, so put viewer-specific customization here rather than in an mpv profile. See Video Preview for playback keys.

ace.artifacts

Field Type Default Description
description_mode str summary Initial Artifacts pane description mode: off, summary, or full. The configured cycle key changes it in memory for the current session only.
panes dict {} Per-pane description overrides keyed by pane id (agents, stitches, patches, beads, files, or provider ids such as ref:plan). description and description_body resolve independently and are both optional for each pane.
relations_expanded bool false Whether the Artifacts relation panel starts expanded; . (toggle_relation_panel) toggles it in memory for the current session only.

ace.artifacts.stitches

ace.artifacts.commits is a deprecated alias for this block; it still loads and warns.

Field Type Default Description
default_query str sidecar:false merges:hide since:24h Initial persistent Stitches query. Supports one configured project name, directory key, or alias in project:; startup may add the current registered project. Accepts repeatable/negatable origin:stitch, origin:auto, or origin:manual, plus repeatable/negatable type: labels including manual, automatic (auto alias), stitch, merge, patch, and observed SASE_TYPE values. merges:hide, merges:show, or merges:only still controls merge visibility. Relative windows re-anchor on refresh; changes apply on next start.

The Stitches pane validates this value with its live query parser. Invalid runtime configuration produces a warning and falls back to the bundled query. An empty configured query is valid and includes sidecars; the visible canonical row renders that state as sidecar:true merges:hide. At startup, an explicit project from sase's TUI query takes precedence over a project: in this setting, which takes precedence over read-only current registered-project inference. The selected project is merged into the query before the pane is composed. project: is singular and cannot be negated or contain an unquoted comma list. It accepts a configured project name, ProjectSpec directory key, or alias, and known committed values are rewritten to the configured name. Once startup merging is complete, no project: token always means a true all-project collection. The Stitches project picker replaces only that token, and All projects removes it while preserving the rest of the query.

Startup injects limit:<ace.page_size> (default 100) when this query has no limit: token. An explicit limit: in the configured string — including limit:all — is left alone, and deleting the token at runtime leaves the list uncapped. When a numeric cap clips the result, sase's TUI keeps the token visible and shows a lower-bound total such as [1/40+] in the repository legend while the filter row says capped. The legend's [P/N] form means selected one-based position over displayed matched entries. limit:all is accepted as an unlimited synonym but is omitted from canonical query text. Day-granular until: values include the full named day. This setting is independent of the sase stitch list CLI's sidecar opt-in and limit contract.

ace.axe_description_expanded

Sets whether the Services-tab description panel for service procs, routines, and jobs starts expanded (true, the default) or collapsed to its summary line in each sase tui session. The toggle_axe_description keymap action — d by default, configurable under ace.keymaps.app — flips the state in memory for the rest of the session; it never writes the toggle back to configuration, so this key is the only durable setting. Descriptions themselves follow the AXE description grammar, and the panel's layout, height budget, and overflow row are described in sase's TUI — Description Panel.

Because the Services tab claims d, the show_diff action is active only on the Patches sub-tab.

ace.current_project

The current project is derived from the head of the VCS macro MRU store — the project you last launched an agent on. sase project set-current and the Projects tab perform the same MRU promotion without a launch.

Field Type Default Description
indicator bool true Show the +<project> chip in sase's TUI top bar, right of the default-model indicator. Governs the top-bar chip only; the Admin Center Projects tab always shows the current project regardless of this setting.
seed_filters bool true Seed project filters that have no value yet. Never overrides an explicit choice or an already-open surface.
seed_agents_query bool false Also seed the active Agents-tab query dialect with the current project's exact project: term when no submitted Agents query, including explicit empty, is remembered.

seed_agents_query is off by default on purpose. The Agents tab is the primary at-a-glance view, and its structured query is also read by unread-jump candidates and prospective-clan selection -- not just the visible list. Turning this on silently re-scopes those surfaces. The capability is fully built; one line of config enables it.

When seed_filters is on, a filter that already has a value — an explicit project: / +name query term, or a pick made this session — is left alone. The Patches query is one of those seeded surfaces: the seed appends a visible project:<name> term for the session only and does not write it to last_query.txt. The Agents query seed is also session-only unless you submit it; it does not update the Agents remembered-query file on its own. A mid-session MRU change moves the chip but does not re-scope surfaces that are already open.

ace.tribes

ace.tribes is keyed by bare tribe name (without @). The special default key configures the reserved @default panel. Every configured tribe entry is required to carry a description; the other fields are optional:

Field Type Default Description
icon str "" Short glyph on structured identity surfaces that already include an icon. Set "" to remove an icon inherited from defaults.
color str "" #RRGGBB foreground for structured tribe icons and names throughout the TUI. Set "" to restore sase's TUI gold fallback.
initially_expanded bool true State applied every time the Agents-tab panel comes into existence.
description str required One-line explanation of the tribe, 1-160 characters. Shown as an unlabeled row beneath the header fields (Name/Status/Composition/Runtime/Fold) when that tribe's Agents-tab panel is selected.

The bundled defaults use ⌂ in sky blue for default, ▲ in lavender-purple for epic, and † in amber-orange for job. They also use ◆ for pinned and ◉ for review, whose identities retain sase's TUI gold fallback; job starts collapsed. Because config entries merge deeply, setting color: "" explicitly clears an inherited color without replacing that tribe's other defaults — overriding only icon or color on a bundled tribe still inherits its bundled description. A manual panel expand/collapse lasts only while that panel remains live in the current sase's TUI session. On restart, or when a tribe panel disappears and later returns, initially_expanded is applied again.

SASE bundles display config only for the tribes its own source assigns (default, epic, job, pinned, review); a tribe your own macros assign with %id(tribe=...), %clan(..., tribe=...), or the #tribe macro has no bundled entry, renders with sase's TUI gold fallback and no icon until you configure it under ace.tribes, and — once configured — requires a description like any other entry.

A missing or blank description on any configured tribe is an error-severity config diagnostic: sase's TUI Config Center refuses to write any change while it is present, not just an edit to that tribe. Run sase doctor -C config.tribes to list every tribe missing a description and the exact ace.tribes.<name>.description key to set.

Identity colors apply only where sase's TUI already has a structured tribe value. They do not scan free-form @... text or recolor selection markers, fold controls, counts, statuses, headings, or explanatory copy. Icons likewise appear only on identity surfaces that already include them; configuring an icon does not add one to compact name-only rows.

sase's TUI reads this TUI setting from the user-level ~/.config/sase/sase.yml (and user overlays), not project-local sase/sase.yml.

ace.notification_tabs

ace.notification_tabs colors, iconifies, and reorders the per-tab counts the top-bar notification indicator renders, keyed by notification-panel tab key. Keys use the user-facing tab names: the synthetic hitl, errors, general, snoozed, and muted tabs (never the internal __snoozed__ / __muted__ spellings), a gate-declared panel name such as beads, or a notification tag.

Field Type Default Description
color str "" #RRGGBB foreground for that tab's count chip and its tooltip label. Set "" to restore the built-in default for the tab.
icon str "" One emoji or display glyph for that tab's chip and tab-strip icon. Set "" to restore the built-in default for the tab.
priority int inherit Sort weight for that tab in the panel strip and the indicator; higher sorts earlier. Omit to inherit the default for the tab's kind.
grouping str "" Row grouping strategy for the notification modal. "" uses the built-in default, and recent opts out to a flat newest-first list.

Colors resolve by precedence, highest first: this setting, then a color the sending gate declared through presentation.color, then the built-in default for a tab sase's TUI ships knowing about, and finally a stable auto-palette entry derived from the tab key. The last rung means a brand-new tag tab is never colorless and keeps the same color across restarts. The bundled defaults are amber-orange hitl, sky-blue attention, red errors, lavender-purple beads, gold general, grey snoozed, and teal muted.

Icons resolve through the same shape, with one deliberate difference at the last rung: this setting, then an icon the sending gate declared through presentation.panel_icon, then the built-in default for a tab sase's TUI ships knowing about, then a default keyed by the tab's own kind (panel or tag, so an unrecognized panel or tag tab still gets a glyph that means something about what kind of tab it is), and finally • for a tab with no kind at all. Unlike color, an icon never falls back to a hashed auto-palette entry — an arbitrary glyph would teach the reader something false, so the chain always bottoms out at a meaningful or honestly generic mark instead. The bundled defaults are ⚑ hitl, ? attention, ✖ errors, ◈ beads, ✉ general, ☾ snoozed, and ⊘ muted.

Configured icons are explicit choices and are never overridden. sase's TUI guarantees distinctness only for SASE-chosen generic icons from the kind and last-resort rungs: on collision it derives an unused ASCII letter or digit from the tab key, and if the key is exhausted it keeps the generic mark rather than inventing one. Run sase doctor -C config.notification_tabs to report two configured tabs that use the same glyph.

Priority is an integer in -1000..1000. Omit the field to inherit the default for the tab's kind; there is no empty-string reset, because 0 is a legitimate value. Writing the number you want at a lower config layer is how an override is cancelled. The defaults restated from the core's tab order are Gates (hitl) 60, any declared panel 50, Errors 40, General 30, Done (a done tag) 20, any other tag 10, Snoozed -10, and Muted -20. The shipped beads default is 0, which drops Beads below every ordinary tab and above the two put-away tabs. A tab whose effective priority differs from its default renders a compact ▴ (raised) or ▾ (lowered) mark in the panel strip and the indicator tooltip.

Grouping controls section headers in the notification modal, not tab membership. The shipped strategy is bead_type for the beads tab, grouping task-bead notifications by task type with Due, Cleanup, and Other fallback buckets. Set ace.notification_tabs.beads.grouping: recent to open Beads flat by default. Press S in the notification modal to toggle the active tab for the current sase's TUI process without rewriting config.

ace.notification_indicator_max_counts

Maximum number of per-tab counts the top-bar notification indicator renders before the remaining tabs collapse into a single dim +N chip. Must be at least 1; defaults to 4. Suppressed tabs are still described in the indicator's hover tooltip.

ace.page_size

Integer step used by Ctrl+J (load more) and Ctrl+K (unload) on sase's TUI lists, and the default Artifacts limit: value when a pane has no explicit cap. Must be at least 1; defaults to 100. Invalid or missing values fall back to 100. Changing this changes the chord step and any default query that had no explicit limit:; it does not rewrite a user-authored limit:40. Launch Control's model-alias history panel is the exception: Ctrl+J / Ctrl+K there always step by 10 runs, independently of this setting and of llm_provider.model_alias_history_limit.

ace.tool_calls

ace:
  tool_calls:
    slow_threshold_seconds: 20
Field Type Default Minimum Description
slow_threshold_seconds int 20 0 Minimum tool-call duration shown as slow in Agents metadata and tool-call timelines.

The threshold drives the SLOW TOOL CALLS section of the Agents metadata header and the slow markers in the Agents Tab LLM Calls Panel. A missing, negative, or non-integer value falls back to 20.

ace.updates

Field Type Default Description
startup_toast bool true Show the startup toast when cached status reports SASE, plugin, or supported agent-CLI updates.
startup_toast_max_commits int 20 Maximum total incoming commit subjects shown across all repositories in the startup toast.
post_update_toast bool true Show a one-shot combined result after an update changes SASE code and restarts sase's TUI.
post_update_toast_diffstat bool true Show per-repository applied file and line-change statistics when available.
post_update_toast_commits bool true Show applied commits grouped by repository when available.
post_update_toast_max_commits int 5 Maximum applied commit subjects shown per repository; 0 keeps totals but hides subjects.
agent_cli_history bool true Show the durable update-history panel beneath the selected Agent CLI row's details.
agent_cli_history_max_rows int 8 Maximum rows in the this-CLI view or runs in the all-CLIs view; 0 shows all history loaded for the pane.
indicator bool true Show the segmented SASE and agent-CLI badge when cached status reports available updates.
prebuild_rust bool true Prebuild exact-match editable sase-core Rust artifacts in the background; update confirmation falls back on every cache miss.
incoming_commits.enabled bool true Fetch and show incoming commit subjects for SASE core and plugin repositories.
incoming_commits.max_per_repo int 7 Maximum incoming commit subjects to show per repository in Updates-tab details.
incoming_commits.confirm_max_per_repo int 250 Maximum subjects fetched per repository in update confirmations; larger ranges show an explicit +N more marker.
check_interval_minutes number 10 Interval between local cached-snapshot revalidation attempts in a running sase's TUI session.
check_ttl_minutes number 10 Minimum age before a startup update check recomputes cached status; this bundled default always wins over the legacy hours key.
check_ttl_hours number unset Deprecated and schema-valid, but currently has no effect in a normal merged config because check_ttl_minutes is always present.
recompute_interval_minutes number 60 Minimum snapshot age before a full SASE/plugin/agent-CLI network recompute; intervening checks only revalidate locally.

Set check_ttl_minutes to change the startup cache TTL. Although check_ttl_hours remains accepted for compatibility, sase's TUI resolves the merged check_ttl_minutes value first; the bundled 10-minute default therefore prevents an hours-only override from taking effect.

ace.keymaps

All TUI keybindings are configurable. The keymaps section has focused modal and pane scopes, app-wide bindings, and prefix-mode maps:

gate — Bindings active in the shared branch controls used by plan and custom gate modals, plus the input panel those modals open when a selection needs typed input:

Field Default Description
next_control j Focus the next branch control.
previous_control k Focus the previous branch control.
toggle_option space Toggle the focused option in an AND group.
submit_primary enter Submit the gate's declared primary branch.
submit_branch ctrl+s Submit the currently active branch and feedback.
open_inputs i Open the input panel for the focused option's note or declared fields.
next_input tab Focus the next field in the gate input panel.
previous_input shift+tab Focus the previous field in the gate input panel.
decision_next l,right Next decision choice, or set yes.
decision_prev h,left Previous decision choice, or set no.
decision_reset r Reset the focused decision row to ★.
decision_reset_all R Reset every decision row to ★.

Gate keys are scoped to the active modal and may overlap app-level bindings. open_inputs is bound on the gate modal and opens the panel even when the selection would otherwise submit immediately, unless the option takes no input. next_input and previous_input dispatch only while that panel is open. activate_control remains accepted as a deprecated alias for submit_primary.

config — Bindings active only while the Admin Center Config hub is focused. The available actions are:

Field Default Description
select_subtab 0 Arm numbered Config-child selection (01-07 when Flags is visible, 01-06 when it is not).

statistics — Bindings active only while the Admin Center Statistics pane is focused. The available actions are:

Field Default Description
prev_view left_square_bracket Select the previous Statistics view.
next_view right_square_bracket Select the next Statistics view.
select_view 0 Arm 01–08 selection for a numbered Statistics view.
jump_to_entry apostrophe Arm the same numbered-view selection used by select_view.
cycle_range t Cycle to the next statistics time range.
cycle_range_reverse T Cycle to the previous statistics time range.
custom_range c Enter a custom statistics time range.
cycle_group g Cycle grouping in the Projects, Macros, or Perf view.
cycle_project_filter p Cycle forward through All and the latest unfiltered project ranking.
cycle_project_filter_reverse P Cycle backward through All and the latest unfiltered project ranking.
focus_macro x Focus one Macro in the Macros Statistics view.
clear_macro_focus X Return the Macros Statistics view to all Macros.
scroll_down ctrl+d Scroll the Statistics body down by half a page.
scroll_up ctrl+u Scroll the Statistics body up by half a page.
refresh r Refresh the active view from its durable data sources.
help question_mark Open contextual Statistics help; the same key closes it.

Statistics keys may overlap app-level bindings because they are registered on the focused pane, not globally.

glossary — Deprecated legacy bindings for the retired standalone Glossary panel, accepted for one release for config-compatibility and always ignored: no defaults ship for this scope and it builds no bindings. sase doctor warns when a loaded config layer sets it explicitly. Move any customization to ace.keymaps.memory below — the Glossary panel's browsing, relation-chip, and add/delete actions now live in the Memory panel, opened the same way (gG / Ctrl+G G, seeded on the glossary term under the cursor).

memory — Bindings active only inside the Memory panel, the browse-and-edit surface for notes, webs, and strands opened from a prompt pane with gm, Ctrl+G m, gG, or Ctrl+G G. A value may list more than one key, separated by commas:

Field Default Description
next_note j Move the note rail cursor to the next note.
prev_note k Move the note rail cursor to the previous note.
first_note g Jump to the first note.
last_note G Jump to the last note.
toggle_web space Expand or collapse the selected memory web.
next_strand s Jump to the next strand in the selected memory web.
prev_strand S Jump to the previous strand in the selected memory web.
scroll_body_down ctrl+d Scroll the note card down by half a page.
scroll_body_up ctrl+u Scroll the note card up by half a page.
filter_notes slash Filter notes by stem and description.
toggle_body_filter greater_than_sign Extend the active filter into note bodies.
next_link tab Focus the next PARENT/CHILDREN (or SEE ALSO/REFERENCED BY on a mention strand) chip.
prev_link shift+tab Focus the previous link chip.
follow_link enter,l Travel to the focused chip's note or strand (or chip ① when none focused).
travel_back backspace,h Walk back one step along the travel trail.
next_scope p Cycle forward through the memory scope ring.
prev_scope P Cycle backward through the memory scope ring.
pick_scope ctrl+p Open the filterable scope picker.
add_note a Open the add-note form, or the add-strand form on a web row.
edit_note e Open the edit form for the selected note's type/parent/description.
delete_note d Confirm and delete the selected note, or the selected strand.
publish I Open the publish confirmation (sase memory init).
open_source o Open the note or strand body in $EDITOR.
open_viewer Z Hand the source file to the artifact viewer.
open_history H Open the selection in the pager at the card's exact version and view (see docs/memory_history.md).
open_changes C Open or close the Changes lens (day-grouped changesets; see docs/memory_history.md).
history_older ( Step the memory card to the older version.
history_newer ) Step the memory card to the newer version.
history_first { Step the memory card to the first version.
history_now } Return the memory card to now.
history_toggle_diff = Toggle the memory card word-diff view (sticky for the pane session).
history_timeline @ Open or close the Timeline lens (the rail becomes the subject's version timeline).
history_compare_base b Set or clear the Timeline lens compare base (pager hand-off carries it).
history_toggle_hidden . Reveal hidden versions in the Timeline lens (chip shortcuts are inert there).
toggle_deleted D Show or hide the DELETED group of tombstoned subjects (see docs/memory_history.md).
mark_reviewed m Mark the Changes lens scope(s) reviewed through the newest changeset (see docs/memory_history.md).
copy_body y Copy the note or strand body to the clipboard.
copy_source_path Y Copy the source path to the clipboard.
refresh r Re-read the current scope.
help question_mark Open the panel-scoped help overlay.

Like gate and statistics keys, memory keys are scoped to the panel and may overlap app-level bindings. . then 1–9 follows a numbered link or relation chip in the Notes lens; that prefix is a fixed pane key. history_toggle_hidden (default .) re-routes the same physical key to the hidden-versions toggle inside the Timeline lens, where chip shortcuts are inert — see the on_key ordering in the Memory pane. A user override of history_toggle_hidden changes only the Timeline lens key, never the Notes chip prefix. greater_than_sign (>) remains a valid configurable key. On the embedded Admin Center Config sub-tab, bare digits remain top-level Admin Center tab selectors.

snippets — Bindings active only inside the Snippets panel, the browse-and-edit surface opened from a prompt pane with gT or Ctrl+G T. A value may list more than one key, separated by commas:

Field Default Description
next_snippet j Move the trigger-list cursor to the next snippet.
prev_snippet k Move the trigger-list cursor to the previous snippet.
first_snippet g Jump to the first snippet.
last_snippet G Jump to the last snippet.
scroll_template_down ctrl+d Scroll the template card down by half a page.
scroll_template_up ctrl+u Scroll the template card up by half a page.
filter_snippets slash Filter triggers and source labels.
toggle_body_filter full_stop Extend the active filter into raw and composed bodies.
next_relation tab Focus the next CALLS / CALLED BY chip.
prev_relation shift+tab Focus the previous relation chip.
follow_relation enter,l Travel to the focused chip's trigger (or chip ① when none focused).
travel_back backspace,h Walk back one step along the travel trail.
next_project p Cycle forward through the enabled-project ring.
prev_project P Cycle backward through the enabled-project ring.
add_snippet a Open the add-snippet form.
edit_snippet e Edit the selected config snippet, or open macro source.
delete_snippet d Confirm and delete the selected writable snippet.
open_source o Open the source in $EDITOR.
open_viewer Z Hand the source file to the artifact viewer.
copy_template y Copy the raw template to the clipboard.
copy_source_path Y Copy the source path to the clipboard.
refresh r Re-read the current project's snippets.
help question_mark Open the panel-scoped help overlay.

Like gate, statistics, and memory keys, snippets keys are scoped to the panel and may overlap app-level bindings. Snippets still uses bare 1–9 for numbered chips and keeps . (full_stop) as the default body-filter toggle.

projects — Bindings active on all three Admin Center Projects-tab sub-tabs (Projects, Repos, Workspaces), so focus_filter, jump_to_entry, reload, and the sub-tab cycle keys stay identical across the three rather than configuring the list separately from the inventories. A value may list more than one key, separated by commas:

Field Default Description
next_option j,down,ctrl+n Move selection to the next row.
prev_option k,up,ctrl+p Move selection to the previous row.
focus_filter slash Filter the active sub-tab.
cycle_subtab right_square_bracket Cycle to the next sub-tab (Projects, Repos, Workspaces).
cycle_subtab_reverse left_square_bracket Cycle to the previous sub-tab.
toggle_project_mark m Toggle the mark on the highlighted project.
clear_project_marks u Clear all marks.
edit_project_spec e Edit the highlighted project's ProjectSpec in $EDITOR.
edit_project_aliases A Edit the highlighted project's aliases.
enable_project a Enable the highlighted project or marked set.
disable_project d Disable the highlighted project or marked set.
delete_project ctrl+d Delete the highlighted or marked SASE project directories.
force_current_state_change F Force the last blocked disable after confirming live-work checks.
default_project_action enter Run the highlighted project's default lifecycle action.
reload R Reload records or the current inventory.
show_project_repos r Show repos pre-filtered to the highlighted project.
show_project_workspaces w Show workspaces pre-filtered to the highlighted project.
jump_to_entry apostrophe Jump to a row via adaptive hints, within the active sub-tab only.
pick_project p Open the shared project picker on the Repos or Workspaces sub-tab.
clear_project_filter escape Clear an inventory project filter.
set_current_project c Make the highlighted project current.
initialize_project i Initialize the highlighted project or marked set.
initialize_all_projects I Initialize every enabled project (sase init --all).

Like gate, statistics, and memory keys, Projects-tab keys are scoped to the pane and may overlap app-level bindings.

machines — focused Admin Center Machines-tab keybindings. These bindings are scoped to the Machines pane and do not become app-level sase's TUI shortcuts.

Field Default Action
next_option j,down,ctrl+n Select the next machine row.
prev_option k,up,ctrl+p Select the previous machine row.
focus_filter slash Focus the machine filter input.
connect_machine c Show the persistent Connect flow.
check_status s Run a bounded authenticated status check for the selection.
repair_machine r Show repair guidance for the selected remote machine.
rename_machine R Show the rename command for the selected remote machine.
remove_machine x Show removal guidance for the selected remote machine.
show_agents enter Open the selected machine's agent tab (or filter Agents when the tab strip is hidden).
show_agents_filter f Open Agents filtered to the selected machine in all cases.
copy_command y Copy the current Machines action command.
reload U Reload the machine inventory.

tool_runs — focused Admin Center Tools-tab keybindings (Runs, Failures, and Catalog views). These bindings are scoped to the Tools pane and do not become app-level sase's TUI shortcuts. A value may list more than one key, separated by commas:

Field Default Action
next_option j,down,ctrl+n Select the next run, group, or catalog row.
prev_option k,up,ctrl+p Select the previous run, group, or catalog row.
focus_filter slash Focus the Runs filter input.
cycle_subtab right_square_bracket Cycle to the next sub-tab (Runs, Failures, Catalog).
cycle_subtab_reverse left_square_bracket Cycle to the previous sub-tab.
toggle_scope A Toggle between the current project and all projects.
focus_detail enter Focus the selected run's detail.
jump_to_agent a Jump to the owning Agents-tab row.
open_log v Open the retained run log in the pager.
copy_run_id y Copy the selected run id.
stop_run s Stop the focused live run (with confirmation).
run_tool r Run the focused catalog tool (with confirmation).
reload R Reload the active view from the run ledger.

Like the Machines-tab keys, Tools-tab keys are scoped to the pane and may overlap app-level bindings.

The Agents-tab copy mode (%) gains a tool run id target on nodes with tool runs, configured as ace.keymaps.modes.copy_mode.keys.agents.tool_run_id (default r, so %r).

app — App-level keybindings. Each key is an action name mapped to a key string. See src/sase/default_config.yml for the full list of configurable actions and their defaults. Rebinding open_config_center also changes the Admin Center's home-page resume key; it does not add a second keymap action or setting.

The Artifacts split actions are remappable as cycle_artifacts_split and cycle_artifacts_split_reverse. Their defaults use right_curly_bracket (}) and left_curly_bracket ({); both curly-bracket key names are accepted anywhere an sase's TUI keybinding is configured.

The Artifacts pane brief cycles with cycle_artifacts_description, default D. It shares that key with the Agents-only toggle_attempt_view; see the shared-key allowlist below.

The Agents header panel toggles with toggle_agent_header, default d. It shares that key with the Artifacts show_diff, the Services toggle_axe_description, and the Stitches stitches_toggle_sdd actions; see the shared-key allowlist below. The toggle is available only on the Agents tab while the header panel is shown.

The Agents jump panel toggles with toggle_agent_jump_panel, default . (full_stop). It shares that key with the Artifacts toggle_relation_panel and the Services toggle_hide_reverted actions; see the shared-key allowlist below. The toggle is available only on the Agents tab while the jump panel is shown.

The Agents show/hide non-run agents toggle is toggle_hide_non_run_agents, default I. It moved off . (which now toggles the Agents jump panel); the Services toggle_hide_reverted keeps . for axe commands.

Agents-tab Enter is act_on_agent, default enter: it opens the selected row's pending gate or jumps to its Patch, and offers a chooser when more than one target applies. The older direct Patch jump remains as jump_to_agent_patch, default unbound. A stale ace.keymaps.modes.leader_mode.keys.jump_to_notification override (the retired ,n chord) is ignored with a warning pointing at act_on_agent.

start_agent_from_patch, default space, prefills the prompt with the most recently launched VCS macro, or opens a blank home-workspace prompt when there is none. The former start_agent_home action is removed; a leftover override for it is ignored as an unknown action. The leader ,h chord still opens a home-context prompt.

Remote Agents actions are also app-level fields. They intentionally ship as unbound: the command palette exposes them contextually, and a configured key becomes active only when the Agents tab and selected remote row support that action.

The top-level Agents query editor is available as the app-level edit_query binding, default /, and the direct agents_filters binding, default f. The leader-mode search_forward chord, default ,/, starts inline deck search on Agents only.

Field Default Action
agents_filters f Open the top-level Agents agents-live filter bar.
agents_refresh r Refresh the Agents tab, or open the Refresh panel while the refresh_panel sunset flag is on.
agents_retry R Retry the selected local or remote agent.
choose_agent_grouping o Open the Agents grouping picker (p/d/s/m modes; local o toggles split/merged panels).
view_agent_metadata V Open the selected local agent's metadata panel in the SASE pager.
connect_agent_machine unbound Open the Admin Center Machines tab.
setup_agent_machine unbound Open the Admin Center Machines tab for enrollment guidance.
retry_remote_agent unbound Compatibility id: retry the selected row on its owning host.
view_remote_agent_content unbound Fetch bounded remote chat, output, or diff content.
answer_remote_attention unbound Answer a pending remote question or approve a pending gate.
check_dispatch_launch_outcome unbound Reconcile the selected provisional remote dispatch-launch row with its operation outcome.

The former toggle_layout, toggle_thinking, toggle_thinking_reverse, choose_agent_view, next_agent_metadata_section, and prev_agent_metadata_section app-key settings are retired. Existing overrides for those names are ignored; the Agents tab now uses deck keys instead (next_deck_card / prev_deck_card for cards, next_deck / prev_deck for decks, pick_deck (p) for the deck picker, toggle_deck_split_below / toggle_deck_split_right for splits).

On the Agents tab, r refreshes and R retries; every other tab keeps r for run_workflow and R for refresh, which is why those pairs share keys (see the allowlist below). With the default-on refresh_panel sunset flag, both refresh actions open the Refresh panel, and the leader full_history_refresh chord (,y) opens it with the cursor on the Agents full-history rescan. Likewise, V opens the Agent Run Log modal (show_agent_run_log) everywhere except the Agents tab, where view_agent_metadata owns it.

Remote retry, stop, and fork reuse the ordinary agents_retry, kill_agent, and edit_hooks actions when the row advertises those capabilities. See Machines.

modes — Prefix-key mode definitions. Built-in modes (fold_mode, copy_mode, leader_mode, bang_mode) can be reconfigured, and custom modes can be added. Each mode has:

Field Type Description
prefix str The activation key for the mode.
keys dict Sub-key definitions. For custom modes, each entry needs a key field and either shell or action.

The built-in fold_mode direct actions are set_level_1 through set_level_3 for PR details and the nested agents.set_level_1 through agents.set_level_4 for Agents metadata. Their defaults produce z1-z3 on PRs; Agents accepts levels 1-2 for a session, 1-3 for a clan or single-agent scope, and 1-4 for a selected whole tribe panel. The configured prefix and subkeys are used by dispatch, the command palette, footers, and help.

Query editing is one app-level action. ace.keymaps.app.edit_query controls Patches, Stitches, Plans, Files, Services, and the top-level Agents query editor, and defaults to bare /. The top-level Agents filter bar also has ace.keymaps.app.agents_filters as a direct alias, default f. Agents metadata search is ace.keymaps.modes.leader_mode.keys.search_forward, default ,/. Stale ace.keymaps.app.search_forward and ace.keymaps.modes.leader_mode.keys.edit_query overrides are ignored with a warning naming those replacements; they are not merged back as runnable commands. Help is an app-level action controlled by ace.keymaps.app.show_help and defaults to bare ?; the retired leader_mode.keys.show_help override is dropped at load time.

Agents panel layout is picker-local now. Configure ace.keymaps.app.choose_agent_grouping to change the opener; with the default opener, oo toggles between tribe-split panels and one merged panel, then closes the picker. The second o is a literal key inside the picker, not a separate configurable app or leader binding. Old ace.keymaps.modes.leader_mode.keys.toggle_agent_panel_grouping overrides are ignored with a warning instead of being remapped onto the picker opener.

The Agents deck picker works the same way: configure ace.keymaps.app.pick_deck (default p) to change the opener. Pressing that opener again inside the picker is the return gesture (last deck, or cycle-previous when the panel has no history) and is not its own binding. The in-picker deck letters (m / f / t / n for Main, Files, Tools, and FINAL) and their capitals (M / F / T / N, which show the deck in the most recently focused other panel with a position glyph hint) are fixed and not configurable. When the opener is a single letter, its capital (P by default) sits next to M / F / T / N and shows the same resolved return deck in the other panel. p is shared with the Artifacts pick_artifacts_project action; the two are disambiguated by tab (see the allowlist below).

The leader update keys are separate remappable actions. update_sase opens the cached Update panel, while update_everything directly runs the same previewed Everything flow as ,U then capital E, including failed-preview and already-current no-op behavior.

A small allowlist of app actions intentionally shares a key because the two actions can never be available on the same surface. Validation permits exactly these pairs and rejects every other duplicate app binding:

Shared key (default) Spelled in YAML as Pair Disjoint because
a a add_axe_item / open_artifact_files Services vs Artifacts
d d show_diff / toggle_axe_description Patches vs Services
d d toggle_agent_header / show_diff Agents vs Artifacts
d d toggle_agent_header / toggle_axe_description Agents vs Services
d d toggle_agent_header / stitches_toggle_sdd Agents vs Artifacts Stitches
E E beads_open_bug / files_open_external Beads vs Files panes (the shared open-externally verb)
w w agents_revive / beads_launch_work Artifacts Agents pane vs Beads pane
w w agents_revive / reword Artifacts Agents pane vs Patches
. full_stop toggle_relation_panel / toggle_hide_reverted Artifacts vs Services
. full_stop toggle_relation_panel / toggle_agent_jump_panel Artifacts vs Agents
. full_stop toggle_hide_reverted / toggle_agent_jump_panel Services vs Agents
X X open_agent_cleanup_panel / patches_toggle_reverted Agents vs Patches
D D toggle_attempt_view / cycle_artifacts_description Agents vs Artifacts
_ underscore (or _) next_query / collapse_all_panel_folds Artifacts query history vs Agents fold sweep
r r agents_refresh / run_workflow Agents vs Patches/Services
R R agents_retry / refresh Agents vs every other tab
o o choose_agent_grouping / cycle_grouping_mode Agents vs Artifacts
p p pick_deck / pick_artifacts_project Agents vs Artifacts
Ctrl+B ctrl+b toggle_deck_focus_reverse / scroll_prompt_up Agents vs Services/Artifacts
Ctrl+Shift+F / > ctrl+shift+f,greater_than_sign swap_deck_panel_next / start_child_mode Agents vs Artifacts
Ctrl+Shift+B / < ctrl+shift+b,less_than_sign swap_deck_panel_prev / start_ancestor_mode Agents vs Artifacts
Ctrl+Shift+D / Ctrl+X ctrl+shift+d,ctrl+x close_deck_panel / prev_chop_run Agents vs Services
Ctrl+T ctrl+t turn_deck_layout / beads_toggle_note_audience Agents vs Artifacts Beads pane
configurable then o varies choose_agent_grouping local panel-layout toggle Agents picker-local o; oo with defaults
configurable varies choose_agent_grouping / cycle_grouping_mode_reverse Agents vs Artifacts

The first column is what the key looks like on your keyboard; the second is the name to write in sase.yml, matching how src/sase/default_config.yml spells it. Punctuation keys generally use their long name (slash, full_stop, question_mark, backslash, vertical_line), the same convention as the curly-bracket names described above; the raw \ and | glyphs are also accepted for the last two.

The allowlist is keyed by action pair, not by key, so moving one of these actions onto a different key keeps the exemption, and pointing a third action at a shared key does not gain it. Only actions you overrode are checked: an override that collides with any action outside its allowed pair is logged as a duplicate and reverted to that action's default, leaving the rest of your keymap in place. The shipped V shared by show_agent_run_log and view_agent_metadata is not on the list: it loads as shipped, but moving both actions onto one new key is logged as a duplicate and both revert to V.

ace.keymaps.app.start_saved_query_mode (default 0) arms direct saved-PR-query slot selection: press it, then a slot digit (1-9, then 0) to load that slot. Its digit sub-keys are not configurable -- they are the slot identifiers themselves, the same way start_checkout_mode's 1-9 workspace digits aren't. The prefix is scoped to the Artifacts tab (any sub-tab); it does not arm on Agents or Services.

Custom mode key fields:

Field Type Required Description
key str yes The sub-key to press after the prefix.
shell str no* Shell command to execute.
action str no* Built-in action name to invoke.

*Exactly one of shell or action must be provided.

The keymap loader validates configuration: invalid keys are reverted to defaults, duplicate bindings within a scope are warned, and prefix conflicts between custom modes and app bindings are detected.

Source: src/sase/default_config.yml, src/sase/ace/tui/keymaps/

ace.snippet_config_path

Names the config file that receives new ace.snippets entries written from the prompt bar — the gt / Ctrl+G t snippet target pane, the gX / Ctrl+G X save panel's snippet mode, and the Snippets panel add form all default to it.

An empty string (the default) resolves to the user's sase.yml — the chezmoi source file under dot_config/sase/ when use_chezmoi is enabled, otherwise ~/.config/sase/sase.yml. A relative configured value resolves against ~/.config/sase/, so sase_snippets.yml means ~/.config/sase/sase_snippets.yml. The path's suffix must be .yml or .yaml; the file itself need not exist yet, but its parent directory must be writable.

A configured value that is unusable — wrong suffix, unwritable parent, invalid YAML, or a project sase/sase.yml that still needs its legacy migration — falls back to the default and reports why: both the gt location picker (as a footer warning) and trigger-name panel and the gX save panel's snippet mode append the reason to the destination line (e.g. configured path unusable: read-only) rather than silently writing somewhere else. When set to a usable file, it is the ★ configured default of the snippet location picker and outranks last-used. The gX save panel additionally always offers the resolved ace.snippet_config_path destination as a selectable, pre-highlighted row — even when it is a custom filename or path that falls outside the standard discovered locations (sase.yml / sase_*.yml under ~/.config/sase/ or the chezmoi equivalent, and the project's sase/sase.yml) — so a configured preference is never silently dropped from the picker.

ace:
  snippet_config_path: "sase_snippets.yml"

See docs/ace.md — Authoring a snippet from the prompt bar.

Source: src/sase/macro/snippet_targets.py

ace.snippets

Defines expandable text snippets for the prompt input widget. Each entry maps a trigger word to a template string. Press Tab in the prompt input to expand the trigger word before the cursor.

ace:
  snippets:
    fix: "Please fix the following issue:\n$0"
    review: "Review this code for correctness, performance, and style."
    plan: "#plan\n$0"

Templates can contain $1, $2, ... tabstops plus $0 for the final cursor position. In sase's TUI prompt input, Tab advances through those stops and Shift+Tab retreats through stops already visited. Expanding a trigger inside an active snippet nests the new snippet's tabstops before the remaining outer stops. Templates can also splice another merged snippet with #[trigger]; use #[trigger(value)] or #[trigger:value] to fill referenced $1, $2, ... tabstops before splicing.

Every effective snippet also gains a generated initial-capital alias: only the first character of the trigger and of the resolved template is uppercased, so foo: "foo bar baz" also exposes Foo → Foo bar baz. Already-capitalized, digit-leading, and underscore-leading triggers produce no extra entry, and an explicitly authored Foo is never replaced. These aliases are runtime-only and are never written back into config. See docs/ace.md — Capitalized aliases for the full rule.

See docs/ace.md — Snippets for usage details and docs/macros.md — Snippet CLI for sase snippet list, show, add, and delete.

Source: src/sase/ace/tui/widgets/prompt_text_area.py

ace.prompt_completion

Controls automatic non-disruptive suggestions and manual prompt-local and prompt-history word completion in sase's TUI prompt input. Suggestions appear in the prompt-bar subtitle and are accepted with Ctrl+L; Enter still submits the prompt as typed. Manual structured/path Ctrl+T completion is independent of the automatic settings, and the Ctrl+R recursive fuzzy file finder is always manual.

ace:
  prompt_completion:
    auto: soft
    debounce_ms: 90
    auto_file_paths: false
    auto_macro_menu: true
    auto_directive_menu: true
    auto_artifact_menu: true
    auto_jinja_menu: true
    max_auto_rows: 1
    history_word_count: 10000
    common_placeholder_count: 100
    word_min_length: 5
    word_ranking: smart
    word_ranking_signals: true
    placeholder_ranking: smart
    placeholder_ranking_signals: true
    next_word: auto
    next_word_max_words: 4
    next_word_confidence: balanced
    next_word_sources: [history]
Field Type Default Description
auto bool/string soft Automatic mode. soft, true, on, yes, or 1 enable subtitle suggestions; false/off disables them.
debounce_ms int 90 Delay before computing a live suggestion after text or cursor changes.
auto_file_paths bool false Allow live suggestions to scan file-path candidates. Manual Ctrl+T file completion still works when false.
auto_macro_menu bool true Automatically open the macro/skill completion menu while typing matching #name, #!name, or /skill tokens.
auto_directive_menu bool true Automatically open directive completion while typing % tokens, fixed values such as %model:, =alias, and ==model shortcuts.
auto_artifact_menu bool true Automatically open the grouped @ reference menu from bare @, narrowed path/kind queries, or @kind: payloads.
auto_jinja_menu bool true Automatically open the Jinja variable completion menu while typing inside a {{ }} or {% %} tag. Manual Ctrl+T still works when off.
max_auto_rows int 1 Reserved row limit for automatic completion modes; current soft mode shows one suggestion.
history_word_count int 10000 Maximum unique recent prompt-history words retained for manual completion; 0 disables the history fallback.
common_placeholder_count int 100 Maximum saved <placeholder> tags retained and offered after prompt-local placeholder matches; 0 disables them.
word_min_length int 5 Shared minimum length for prompt-local and prompt-history word candidates; values below 1 clamp to 1.
word_ranking string smart History-word ordering. smart ranks by relation, recency, and frequency; recent restores plain most-recently-used order.
word_ranking_signals bool true Whether smart-ranked history-word rows render the score meter, dominant-reason chip, and panel legend.
placeholder_ranking string smart Saved-placeholder ordering. smart ranks by relation, recency, and frequency; recent restores stored count-then-recency order.
placeholder_ranking_signals bool true Whether smart-ranked saved-placeholder rows render the score meter, dominant-reason chip, and panel legend.
next_word string auto Next-word prediction mode. auto predicts after each typed character and completes the current word while you type it. chain arms a ghost or border peek only after word commits and on explicit Ctrl+T. off disables predictions.
next_word_max_words int 4 Maximum words in one ghost or peek; clamped to 1–8. A mid-word continuation counts the completed word toward this cap.
next_word_confidence string balanced Confidence gate for ghost predictions: cautious, balanced, or eager.
next_word_sources list [history] Prediction corpus sources. Add archive to also learn from the pruned cross-machine prompt archive at low weight; history is always kept. Unknown entries are dropped.

The minimum applies to the complete candidate, so a shorter typed prefix can still complete an eligible word. Prompt-local words below the threshold are skipped before sase's TUI considers the prompt-history fallback. Candidates from history retain their original spelling and, under the default word_ranking: smart, are ordered by a weighted composite of how strongly each word relates to the words already in the prompt (0.50), how recently it was used (0.30), and how often it was used (0.20). Setting word_ranking: recent restores plain most-recently-used order. A warm next-word model promotes current-word matches ahead of that order only while word_ranking is smart and next_word is not off. recent leaves the menu on its non-prediction order. The warm cache holds the prompt-word index off-thread and is rebuilt when history shards or the shared minimum change, while Ctrl+D deletions apply at query time without a rebuild. Setting history_word_count: 0 disables only the history fallback; eligible prompt-local words remain available. See the History-word completion bullet in docs/ace.md for the score meter, reason chip, and legend that word_ranking_signals controls.

Common placeholders are stored at sase_home()/prompt_placeholders.json and are learned from complete raw <foobar> tags outside literal zones in submitted, failed-launch, and cancelled prompt drafts. When the store is first created, sase's TUI seeds it once from bounded prompt history so existing tags can appear immediately. Retention evicts least-recently-used entries down to common_placeholder_count. By default (placeholder_ranking: smart) the < menu ranks saved tags by relation to the prompt being edited, recency, and frequency; placeholder_ranking: recent restores the stored count-then-recency order. Setting common_placeholder_count: 0 disables recording, loading, and display of saved placeholders; prompt-local placeholder completion still works.

The former history_word_min_length key has been replaced by word_min_length. Existing overrides must rename the key to keep controlling word completion.

The +query project/Patch picker uses the same completion panel and opens when the plus is at the start of the prompt or directly follows whitespace, {, or |. It is not disabled by auto_macro_menu. Manual Ctrl+T project/Patch completion uses the same token rule and works regardless of these automatic-completion settings.

@ reference completion uses a project-scoped artifact catalog and warm prompt path inventory. auto_artifact_menu controls automatic opening of the grouped menu from bare @, narrowed artifact-kind or local-path queries such as @pl and @src/, and @kind: payload contexts; manual Ctrl+T remains available. Before a : appears, local file rows stay hidden while the query prefix-matches an artifact kind (including bare @). The panel's [^T] files hint marks that state; the first Ctrl+T reveals files without completing the kind, and a later press completes normally. Queries with no kind prefix match show file rows automatically. File rows preserve the @ sigil on insertion, directories drill down, and dotfiles are hidden unless the typed path segment starts with .. A cold path inventory can briefly show a loading row while sase's TUI refreshes it off-thread. Document, chat, indexed-file, bead, and agent payloads use bounded project-scoped catalogs; commit and bug candidates are projected only from already-loaded Artifacts-pane snapshots.

The %model: / %m: value menu and the =alias / ==model model shortcut menus are also controlled by auto_directive_menu. The %model: menu lists inline-typable model names, the five built-in size aliases (@xsmall, @small, @medium, @large, @xlarge), configured model aliases, and provider drill-down rows. Provider short aliases are shown as filter/display hints but are not inserted. The =alias shortcut is alias-only: accepting =la on @large rewrites the token to %m:@large. The ==model shortcut lists concrete model rows, so accepting ==gpt can rewrite the token to %m:gpt-6.1-sol; provider-qualified input such as ==codex/g narrows to that provider. The old *alias and **model forms remain ordinary prompt text. Ctrl+T remains available for both shortcut menus when automatic directive menus are disabled.

File-path completion roots relative lookups in the prompt-selected workspace. A +<project> project tag, registered workspace-provider refs, and known-project refs such as #git:<project> or #gh:<owner>/<repo> can root lookup in that project checkout. If no prompt workspace ref resolves, lookups fall back to the TUI process directory. These root rules are shared by live path suggestions, manual Ctrl+T path completion, and the manual Ctrl+R recursive finder.

Source: src/sase/ace/tui/widgets/prompt_completion.py, src/sase/ace/tui/widgets/_prompt_soft_completion.py, src/sase/ace/tui/widgets/history_word_completion.py, src/sase/history/prompt_word_index.py, src/sase/history/prompt_word_ranking.py, src/sase/history/prompt_placeholders.py, src/sase/ace/tui/widgets/prompt_completion_root.py, src/sase/ace/tui/widgets/recursive_file_finder.py

ace.prompt_spellcheck

Controls the sticky misspelling highlight in sase's TUI prompt input. Every word K proves misspelled (an aspell misspelled verdict) is remembered durably and underlined in every prompt input from then on; K on a word already remembered is what teaches sase's TUI about it, not a background spell-checker.

ace:
  prompt_spellcheck:
    highlight: true
    max_remembered_words: 5000
Field Type Default Description
highlight bool true Whether remembered misspellings are underlined in prompt inputs. K still records and clears misspellings when false.
max_remembered_words int 5000 Maximum words retained in each of the misspelled and accepted-word lists; 0 disables remembering new misspellings.

Remembered words are stored at sase_home()/prompt_misspellings.json, casefolded for matching but keeping the first-seen spelling. Pressing a in the correction panel accepts a word instead of applying a suggestion, moving it into the accepted list so it is never flagged again; K on an already-correct remembered word (for example, after adding it to your aspell personal dictionary) clears it automatically. Retention evicts the oldest entries once a list exceeds max_remembered_words.

Source: src/sase/history/prompt_misspellings.py, src/sase/core/word_lookup.py, src/sase/ace/tui/widgets/_misspelling_highlight.py, src/sase/ace/tui/actions/_startup_misspellings.py, src/sase/ace/tui/modals/spellcheck_panel_modal.py

ace.prompt_stash

Controls Stash Trash recovery in sase's TUI Prompts overlay (see Prompts Overlay). Trash recovers only drafts deliberately discarded from Stash, up to the configured row limit.

ace:
  prompt_stash:
    trash_limit: 100
Field Type Default Description
trash_limit int 100 Maximum Trash rows kept. An entry-count limit, not a byte quota. 0 disables Trash recovery and permanently discards rows marked for Trash (rows stay recoverable with sase prompt stash-archive). Must be an integer >= 0; booleans and malformed values fall back to 100.

A lowered limit is applied the next time the overlay opens. Over-limit rows are permanently deleted, oldest discarded first, and a toast reports how many were deleted. The Trash list in that same window still shows the rows from before the deletion. Enter on one of those already-deleted rows does not bring it back. The Trash view then repaints from the store, so every already-deleted row disappears. Close the overlay and open it again to see the rows that remain without pressing Enter. See Prompts Overlay for the toast text and the failure case. Trash requires the current stash core binding: restart old TUI processes before the new behavior takes effect, since mixed-version operation is unsupported.

Source: src/sase/ace/config.py, src/sase/core/prompt_stash_facade.py, src/sase/ace/tui/actions/agent_workflow/_prompt_bar_stash_restore.py

ace.agent_decks

Controls whether a multi-card agent data deck renders spread (every card on one scrollable page) or paged (one card at a time) on the Agents tab deck panels, under the automatic deck view (see Deck Views). A fixed deck view bypasses these thresholds entirely.

ace:
  agent_decks:
    spread_max_screens: 1.5
    block_spread_max_screens: 1.5
    final_tail_delay_seconds: 5.0

Jump to any deck with p then its picker letter: m Main, f Files, t Tools, n FINAL (capitals open it in the other panel). See Agent data decks and cards and Finalizers on the Agents tab.

Field Type Default Description
spread_max_screens number 1.5 A multi-card deck renders spread when its cards fit within this many panel viewport heights ("screens"), and paged otherwise. 0 means always paged. Applies to the automatic deck view only; a fixed view bypasses measurement.
block_spread_max_screens number 1.5 A card shown alone renders its card blocks spread (all inline) when the card fits within this many panel heights, and paged (one block per page) otherwise. 0 means always one block per page. Applies to the session Reply card's per-turn blocks. Applies to the automatic deck view only; a fixed view bypasses measurement.
final_tail_delay_seconds number 5.0 Seconds a finalizer op must run before its live tail renders in the ⊛ FINAL deck. 0 renders immediately; fast ops go straight from running to done with no tail. Non-numbers, negatives, and booleans fall back to the default.

Source: src/sase/ace/tui/agent_decks_settings.py

ace.agent_header

Controls how many rows of the agent's AGENT MACRO the collapsed Agents-tab header panel previews below its two chip rows. The preview shows at most collapsed_preview_max_rows rows, and fewer when collapsed_max_share leaves less room on a short column.

ace:
  agent_header:
    collapsed_max_share: 0.35
    collapsed_preview_max_rows: 3
Field Type Default Description
collapsed_max_share number 0.35 Share of the detail-column height the collapsed header may take (0 to 0.6). The preview gets that cap minus the border, chip rows, and MACRO tab row, at least 1 row. 0 turns the preview off.
collapsed_preview_max_rows integer 3 Most macro preview rows the collapsed header shows (at least 1). The collapsed_max_share budget can lower it on short columns. d expands to the full prompt.

Source: src/sase/ace/tui/agent_header_settings.py

ace.agent_tabs

Agent tab settings for the Agents tab. machine_tabs selects machine-tab mode: auto shows one machine tab per configured dispatch machine (and a single roster when no machines are configured), on always uses machine tabs, and off never does. tabs holds per-tab styling and ordering keyed by canonical tab name; invalid names are dropped.

ace:
  agent_tabs:
    machine_tabs: "auto"
    launch_from_view: true
    tabs:
      sase:
        color: "#AF87FF"
        icon: "◈"
        order: 0
        description: "SASE project agents."
Field Type Default Description
machine_tabs string "auto" auto uses machine tabs when dispatch machines exist, on always does, off never does. Unknown values fall back to auto.
launch_from_view bool true Launch new agents onto the currently visible tab. Non-boolean values fall back to true.
tabs dict {} Per-tab color, icon, order (integer or null), and description, keyed by canonical tab name.

Source: src/sase/ace/tui/agent_tabs_settings.py

ace.prompt_submission

Controls whether plain Enter asks for confirmation before launching agent prompts from sase's TUI prompt input.

ace:
  prompt_submission:
    confirm_on_enter: true
Field Type Default Description
confirm_on_enter bool true When true, plain Enter opens the submission panel and Enter inside that panel confirms its primary launch action.

When enabled, the panel appears for non-empty ordinary single-pane prompts, targeted single-pane drafts, and multi-pane prompt stacks. g<enter> in NORMAL mode and Ctrl+G Enter in INSERT mode stay direct selected-pane launch routes. Set confirm_on_enter: false to restore immediate plain-Enter launch: a single pane sends normally, a stack submits the active pane, and a targeted pane launches instead of opening the launch/write/save chooser.

Source: src/sase/ace/tui/prompt_submission_settings.py, src/sase/ace/tui/widgets/_prompt_text_area_key_handling.py, src/sase/ace/tui/modals/prompt_submit_choice_modal.py

ace.prompt_inputs

Controls how sase's TUI treats raw <placeholder> tags when a prompt is submitted or saved as a macro.

ace:
  prompt_inputs:
    collect_raw_placeholders: true
    macro_placeholder_args: true
Field Type Default Current behavior
collect_raw_placeholders bool true When true, submitting an sase's TUI prompt opens Fill in this prompt for each live raw placeholder. When false, raw tags launch unchanged; declared input: collection still works.
macro_placeholder_args bool true When false, gX, gL, and fresh gx extraction keep live raw tags as literal text and mint no placeholder-derived text inputs. Jinja-variable input inference for gL is unaffected.

Raw placeholders in YAML frontmatter, inline code, fenced code, or %macros_enabled:false regions are never collected. See Raw Prompt Placeholders for the submit panel, literal-tag control, and macro conversion workflow.

Source: src/sase/agent/prompt_placeholder_inputs.py, src/sase/ace/tui/actions/agent_workflow/_launch_start.py, src/sase/ace/tui/actions/agent_workflow/_prompt_bar_save_macro.py, src/sase/ace/tui/widgets/_prompt_input_bar_local_macro_actions.py, src/sase/ace/tui/widgets/_local_macro_conversion.py

artifacts

Bounds on automatic artifact capture at agent finalization, and the opt-in retention policy that bounds the store afterwards. Capture keeps bytes only for files a run authored that version control cannot reproduce; content already reachable from a durable commit becomes a byte-free reference row. See VCS-Backed Artifact Files for the decision matrix and the vcs-cache directory, and Store Lifecycle for the report → dry run → opt-in retention progression that artifacts.retention completes.

artifacts:
  capture:
    max_stored_per_agent: 50
    max_history_scan: 20
    max_file_size_bytes: 104857600
    pool_max_bytes: 1073741824
Field Type Default Minimum Description
artifacts.capture.max_stored_per_agent int 50 1 Maximum byte-copying automatic captures per agent run. Reference rows cost no bytes, are uncounted, and uncapped.
artifacts.capture.max_history_scan int 20 1 Durable commits searched per file when looking for one holding its exact content.
artifacts.capture.max_file_size_bytes int 104857600 1 Maximum size of one file copied into the workspace-local prompt-artifact pool; larger files are hashed and recorded instead.
artifacts.capture.pool_max_bytes int 1073741824 1 Workspace-local prompt-artifact pool budget before opportunistic garbage collection removes published terminal-run copies.

These fields are read fail-open: a missing, non-integer, or out-of-range value falls back to the built-in default rather than failing capture. Once max_stored_per_agent is reached, the remaining byte-copy candidates are skipped and finalization reports cap_fired=true on its [artifacts] default capture: summary line. Raising max_history_scan widens the bounded search that recovers content whose recorded commit was squash-rewritten, at the cost of a longer walk per file.

max_file_size_bytes and pool_max_bytes apply to launch-time prompt-artifact staging under the workspace-local .sase/artifacts/ tree. The local manifest still records oversized files, but SASE does not copy their bytes into .sase/artifacts/pool/. See Prompt Artifact Staging and Archive for the layout and garbage-collection rules.

artifacts.retention is the opt-in policy that runs once after each agent finalization, immediately after automatic capture. It ships disabled with generous values pre-filled, so enabling it later is a flag flip rather than a policy design exercise.

artifacts:
  retention:
    enabled: false
    empty_shard_removal_budget: 2000
    keep_per_label: 3
    keep_recent_run_months: 2
    max_age_days: 90
    trash_grace_days: 14
Field Type Default Minimum Description
artifacts.retention.enabled bool false - Run the artifact-file retention pass after agent finalization. While false, retention removes nothing.
artifacts.retention.empty_shard_removal_budget int 2000 1 Max empty ace-run month/day/run shard directories listed by one sase artifact prune-runs preview.
artifacts.retention.keep_per_label int 3 0 Newest automatic captures kept per label; older generations are trashed first. 0 disables the predicate.
artifacts.retention.keep_recent_run_months int 2 1 Calendar months of ace-run directories kept whole by sase artifact prune-runs.
artifacts.retention.max_age_days int 90 0 Trash automatic captures created more than this many days ago. 0 disables the predicate.
artifacts.retention.trash_grace_days int 14 0 Days a trashed artifact stays restorable before a purge removes it.

These fields are read fail-open the same way the capture fields are. The pass is bounded and defensive: it never fails a run, and it removes nothing that retention's protection contract keeps — explicit artifacts, artifacts referenced by a ProjectSpec, plan, bead, or research document, artifacts recorded in the consumption ledger, and the newest capture of every label. If any required protection source cannot be read, the whole pass is skipped rather than under-protecting, and finalization prints [artifacts] retention skipped: protection sources unavailable: <sources>. Otherwise it prints one [artifacts] retention: line with rows trashed, bytes reclaimed, and trash entries purged.

The same values drive the manual surfaces, so a dry run previews exactly what enabling retention would do: keep_per_label is what sase artifact prune plans with when -g is omitted, both predicates define the default-policy selection sase artifact stats reports last, and trash_grace_days is the cutoff sase artifact trash purge honors without -a/--all and the one trash list marks entries against. Setting both predicates to 0 leaves a policy that selects nothing.

keep_recent_run_months drives sase artifact prune-runs, which previews old terminal artifacts/ace-run/ directories plus empty month/day/run shards outside sase's TUI startup watch window. Deletion is currently preview-only: --apply fails closed before removing run directories, empty shards, or artifact-index rows. The preview protects recent months, incomplete runs, referenced agent names or paths, artifact-file producers, and runs tied to non-closed beads. empty_shard_removal_budget bounds how many empty shard directories one preview lists.

Source: src/sase/config/core.py, src/sase/core/artifact_capture_policy.py, src/sase/core/artifact_file_retention.py, src/sase/core/agent_artifact_run_retention.py, src/sase/axe/run_agent_exec_finalize.py

artifact_refs

Allow-lists path-backed @file:<path> prompt references. Indexed @file:default:<digest> and @file:explicit:<digest> references do not need these roots; this section controls only references authored as absolute or ~/ paths.

artifact_refs:
  file:
    roots:
      - name: notes
        path: ~/notes
        path_globs: ["**/*.md", "!private/**"]
      - name: reports
        path: /srv/reports
Field Type Required Description
artifact_refs.file.roots[].name string yes Stable lowercase slug used in published logical identities.
artifact_refs.file.roots[].path string yes Absolute or ~/-rooted allow-list directory.
artifact_refs.file.roots[].path_globs list[string] no Root-relative POSIX includes and !-prefixed exclusions.

Root lists concatenate across ordinary config layers. A layer using the global list-replacement merge strategy replaces the inherited list instead. When the same name appears more than once, the later root overrides the earlier definition without changing its position. Invalid entries are skipped with a warning rather than disabling all usable roots.

Resolution accepts existing regular files only, requires the path to stay within an effective root and its glob policy, and never interprets relative paths against the current directory. At launch SASE snapshots accepted bytes into the workspace-local artifact pool, so subsequent changes to the source file do not change the agent's captured input. The normal artifacts.capture.max_file_size_bytes limit applies.

Run sase doctor -C config.artifact_refs to find malformed, missing, nested, overlapping, or zero-usable-root configurations. See Artifact References for prompt syntax and context rules.

llm_provider

Configures which LLM backend sase uses and how size aliases map to concrete models. See docs/llms.md for the full LLM provider architecture, preprocessing pipeline, and invocation lifecycle.

llm_provider:
  provider: claude # or "codex", "qwen", "opencode", "agy", "muse", "grok", "fakey" (default: auto-detect)
  # Scalar launch settings. Same grammar as %model; may reference a built-in
  # size alias.
  default_model: "@large" # used when a launch has no %model directive
  epic_lander_model: "@large" # epic land agents below bead.big_epic_phase_threshold
  big_epic_lander_model: "@xlarge" # epic land agents at/above that threshold
  model_alias_history_limit: 10 # runs shown per alias in Launch Control history
  # Override examples; shipped size-alias defaults are generated in docs/llms.md.
  model_aliases:
    builtin:
      medium: codex/gpt-6.1-sol # specialize the medium size alias
      large: claude/opus | codex/gpt-6.1-sol # custom large-phase pool
    custom:
      blogger:
        model: claude/opus
        description: Agents that draft and edit blog posts.
        bucket: writing # optional Launch Control grouping
    buckets:
      writing:
        description: Writing and editing roles.
Field Type Default Description
llm_provider.provider string auto-detect Which registered provider to use. Auto-detects by plugin-declared priority; built-ins default to claude → codex → qwen → opencode → agy. muse and grok declare no priority and are never auto-detected; name them explicitly.
llm_provider.model_tier_map dict - Accepted by the config schema for compatibility. No runtime path currently reads it; setting large/small here has no effect. Size aliases and default_model / lander settings select models.
llm_provider.default_model string @large Model expression used when a launch has no explicit %model directive.
llm_provider.epic_lander_model string @large Model expression used by epic land agents when the epic has fewer authored phases than bead.big_epic_phase_threshold.
llm_provider.big_epic_lander_model string @xlarge Model expression used by epic land agents when the epic has bead.big_epic_phase_threshold or more authored phases.
llm_provider.default_effort string "" Reasoning effort applied when a launch requests none: none, minimal, low, medium, high, xhigh, or max. Empty leaves each provider on its own default; unsupported levels are skipped with a warning.
llm_provider.model_alias_history_limit int 10 Maximum prior runs returned per alias for the Launch Control agent-history panel. Must be at least 1; malformed runtime values defensively fall back to 10.
llm_provider.model_aliases.builtin dict - Overrides for the five built-in size aliases (xsmall, small, medium, large, xlarge). Values use the single-target grammar, \| round-robin pools, \|\| ordered fallbacks, or (A \| B) \|\| C last-resort.
llm_provider.model_aliases.custom dict - User-defined aliases usable from %model:@<alias> / %m:@<alias>. Each requires model (single target or selector) and description.
llm_provider.model_aliases.buckets dict - Optional display-only sase's TUI Launch Control bucket descriptions.

Model aliases are resolved when an agent launches, so reusable macros can point at names such as %model:@medium or %model:@blogger while each user's sase.yml controls the concrete provider/model. Alias config keys stay bare; the @ marker is only used in %model/%m directive values. Alias values may reference another alias with @<alias>; the reference may carry a trailing effort such as @medium@high, which overrides the referenced alias's effort (chains are followed with cycle/depth protection). Unknown non-alias model values keep the existing fallback behavior and run on the default provider. Use model_aliases.builtin to override one of the five built-in size aliases and model_aliases.custom for user-defined aliases with descriptions. A | B round-robins across real launches, skips providers whose CLI is unavailable, and stores its machine-global cursor in ~/.sase/llm_lb.json; display and preview surfaces only peek. A || B always selects the first installed provider CLI that is not hard-disabled (a soft-disabled first candidate still wins) and never reads or advances that cursor. (A | B) || C load-balances the parenthesized pool and uses the || tail only when every pool member is unavailable (CLI missing or hard-disabled); an all-soft pool still rotates and does not divert, and tail selection does not consume the pool cursor. Unparenthesized mixing is still rejected. Ordered fallback is based on CLI installation plus temporary provider-disable state, not later model/runtime success, and preserves its first candidate for normal diagnostics when none are available. Members may carry a trailing effort. Selectors cannot be nested, and selectors are not accepted in %model directives or launch-scoped/temporary overrides. In sase's TUI Launch Control, the pool row reports the available/total count, selector member lists mark the current selection with →, and active temporary overrides label selection suspended unless their provider is hard-disabled; then the override is paused and the underlying alias resolves. A soft disable does not pause the override.

On top of any configured aliases, SASE ships a fixed set of built-in size aliases that resolve even when unset: @xsmall, @small, @medium, @large, and @xlarge. Each is a direct selector with no fallback chain to another alias — override one by setting model_aliases.builtin.<size> to a concrete model, an A | B round-robin pool, an A || B ordered fallback, or a parenthesized (A | B) || C last-resort. Phase and task launches route directly to the size alias matching their size metadata; a legacy phase or task with no size metadata routes through @small. New tasks require an explicit size after /sase_new_task has ruled out a semantic duplicate and a causally related in-progress epic. See Built-in size aliases for the full shipped-defaults table and Role Aliases for Delegated Work for how delegated launches pick a model.

Three scalar llm_provider fields choose the model for launches that aren't driven by phase/task/tale size routing: default_model (used when a launch has no explicit %model directive), epic_lander_model (used by epic land agents below bead.big_epic_phase_threshold authored phases), and big_epic_lander_model (used by epic land agents at or above that threshold). Each accepts the same grammar as an alias target — a concrete model, a provider-qualified model, an @alias reference (optionally with a trailing effort such as @large@high), an A | B pool, an A || B fallback, or a parenthesized (A | B) || C last-resort. Precedence is unchanged from before the migration: an explicit prompt/plan/phase/task/approval-picker %model wins first, then an active temporary override of the selected setting, then the config field resolves through the normal alias/effort/selector/provider-disable machinery, and a missing or malformed field falls back to its shipped default (@large / @large / @xlarge).

model_alias_history_limit bounds the number of prior runs requested for each alias in Launch Control's agent-history panel. It defaults to 10, must be at least 1, and falls back to 10 at runtime when a malformed value bypasses schema validation. From that initial window, the panel's Ctrl+J / Ctrl+K keys add or subtract a fixed 10-run step and never drop below this configured limit; they do not use ace.page_size.

Accepted tale follow-ups without an approval-time model validate the actual handoff plan and choose the matching size alias directly. Legacy tale plans without size metadata use @medium. An approval-time model, a %model directive in a custom coder prompt, or an outer effort suffix remains authoritative.

model_aliases.builtin.epic_creator is retired. SASE no longer launches an epic-creator agent, resolves that alias implicitly, or treats it as a builtin override, so a stale entry should be deleted rather than repointed. sase doctor reports a leftover entry under the model_aliases.builtin.epic_creator key.

The llm_provider.worker_models map and the reserved @worker / @other aliases were removed in epic sase-5d. Use a size alias (@xsmall, @small, @medium, @large, @xlarge) or an explicit model instead of @worker, and llm_provider.default_model instead of @other. The phase_worker bucket and its <size>_phase_worker aliases from that epic were themselves retired by the later size-alias simplification below. sase doctor reports configs that still reference removed keys or aliases, including retired @coder and registered @<provider>_coder builtin entries.

The implicit role aliases (@default, @epic_lander, @big_epic_lander), the five <size>_worker aliases, the automatic worker bucket, and the capability/cost aliases (@smart, @smarter, @smartest, @cheap, @cheaper, @cheapest) were removed in favor of the five direct built-in size aliases above plus the three scalar default_model / epic_lander_model / big_epic_lander_model fields. A custom alias can still opt into a bucket of any name, including worker — it just has no special behavior anymore. Run sase doctor -C config.model_aliases for the exact destination of any retired name still present in your config or directives.

The TUI also supports temporary, per-alias session-level provider/model overrides (set from Launch Control, ,m) that do not edit this config. They are persisted to ~/.sase/llm_override.json and expired entries are deleted on next read. See docs/llms.md for the resolution order, state-file format, and precedence relative to SASE_MODEL_TIER_OVERRIDE.

The same panel's p=Providers flow manages temporary provider disables in ~/.sase/llm_provider_disables.json. This is runtime state, not configuration: it does not add a disabled_providers key, does not edit llm_provider.provider, and does not rewrite any alias. A hard disable is fail-closed: alias selectors skip that member, temporary alias overrides targeting it pause until the disable clears or expires, and direct explicit provider/model requests fail with an actionable diagnostic instead of silently switching providers. A soft disable is spared in | pools while another member can cover, never diverts a || fallback, and does not pause overrides or fail explicit requests. See Temporary Provider Disables.

llm_provider.usage_limit

Automatic usage-limit detection temporarily disables a provider when that provider's own error output positively matches its configured usage-limit patterns. This is separate from llm_provider.retry: provider-scoped usage-limit classification wins before retry policy, while a plain transient 429 or other retryable error that does not match a usage-limit pattern still follows retry/fallback policy.

llm_provider:
  usage_limit:
    enabled: true
    disable_seconds: 86400
    min_disable_seconds: 60
    max_disable_seconds: 604800
    honor_reset_hint: true
    honor_usage_windows: true
    notify: true
    relaunch: true
    relaunch_limit: 20
    providers:
      claude:
        patterns:
          - "you've hit your usage limit"
        exclude_patterns:
          - "usage limit approaching"
        replace_patterns: false
        disable_seconds: null
        honor_reset_hint: null
        honor_usage_windows: null
Field Type Default Description
llm_provider.usage_limit.enabled bool true Enable usage-limit classification and automatic provider disables.
llm_provider.usage_limit.disable_seconds int 86400 Fallback disable duration in seconds used only when neither a provider reset hint nor a corroborated usage-window reset is available.
llm_provider.usage_limit.min_disable_seconds int 60 Lower bound applied to provider-reported reset-hint and usage-window-derived durations.
llm_provider.usage_limit.max_disable_seconds int 604800 Upper bound applied to provider-reported reset-hint and usage-window-derived durations.
llm_provider.usage_limit.honor_reset_hint bool true Parse a provider-reported reset time when present: a bare or zoned clock time ("resets at 8pm", "resets 6:38pm (America/New_York)"), an absolute date ("try again at Aug 20th, 2026 6:38 AM", "resets Aug 22, 8pm (America/New_York)", "resets 2026-08-20 06:38 UTC"), or a relative duration ("try again in 2 hours").
llm_provider.usage_limit.honor_usage_windows bool true When no reset hint is found, fall back to a corroborated reset from the collected usage-window data (see Usage-Limit Auto-Disable) before using the flat disable_seconds.
llm_provider.usage_limit.notify bool true Send one notification for each newly-created automatic disable window.
llm_provider.usage_limit.relaunch bool true Behind the provider_drain beta flag: submit a durable sase agent drain proc when a usage-limit disable hard-disables a provider, relaunching the agents it stranded and folding the report into the notify notification. Ignored while the flag is off.
llm_provider.usage_limit.relaunch_limit int 20 Most agents an automatic drain relaunches (passed as the drain's --limit).
llm_provider.usage_limit.providers.<provider> dict - Provider-specific detection and duration overrides.
llm_provider.usage_limit.providers.<provider>.patterns list[str] [] Positive case-insensitive substring patterns. User patterns are additive with provider defaults unless replacement is set.
llm_provider.usage_limit.providers.<provider>.exclude_patterns list[str] [] Case-insensitive exclusions that suppress otherwise positive matches; exclusions are always additive.
llm_provider.usage_limit.providers.<provider>.replace_patterns bool false When true, the configured patterns list literally replaces built-ins; patterns: [] intentionally disables that detector.
llm_provider.usage_limit.providers.<provider>.disable_seconds int/null null Per-provider fallback duration used only when neither a reset hint nor a corroborated usage-window reset is available; null inherits llm_provider.usage_limit.disable_seconds. grok ships a non-null built-in default of 172800 (48h): Grok Build reports no reset instant in any usage-limit message, and paid usage meters against one shared weekly pool, so this now-usually-preempted flat default still matters when the collector has no fresh corroborating window.
llm_provider.usage_limit.providers.<provider>.honor_reset_hint bool/null null Per-provider reset-hint policy; null inherits the global value.
llm_provider.usage_limit.providers.<provider>.honor_usage_windows bool/null null Per-provider usage-window fallback policy; null inherits the global value.

Automatic disables are written to the same machine-wide provider-disable state used by Launch Control, with source: "usage_limit". They expire and self-clean like manual disables, can be cleared early from Launch Control, and do not unregister or rewrite a provider. If a fallback is available, retry/fallback may proceed only to a different enabled provider; the disabled provider remains skipped until expiry or clearing. See Usage-Limit Auto-Disable for reset-hint parsing, notification, and replacement details.

llm_provider.usage_metrics

Subscription-capacity collection settings. This is distinct from llm_provider.usage_limit, which classifies provider error text and writes temporary routing disables. usage_metrics controls whether SASE probes installed provider CLIs for included-allowance windows.

enabled is the durable user preference; setting it false stops probes, isolated workers, passive observation writes, scheduled requests, and attention.

Claude, Codex, Grok, Muse Code, and Antigravity ship collectors. Muse's is a free local probe against muse serve — no model call and no tokens — so it costs only about three seconds of wall clock per refresh. To turn it off, set llm_provider.usage_metrics.providers.muse.enabled to false; see Muse Code subscription usage. Antigravity's is likewise a free local /usage probe (agy >= 1.1.11, about three to five seconds per refresh); see Antigravity subscription usage.

Every recorded attempt carries its outcome, failure reason, and any Retry-After hint, and refresh backoff follows the failure class. sase usage list -v shows the per-provider retry state; JSON output passes the collector_health snapshot through unchanged.

Class Failure reasons Retry policy
Success ok, not_applicable, unsupported outcomes next probe at the provider cadence
Transient timeout, deadline_exceeded, probe_failed, parse_error, malformed_payload, account_context_changed cadence-based backoff up to 30 minutes
Rate-limited rate_limited honored Retry-After (clamped to 15 minutes–6 hours) when present, otherwise an escalating delay from 15 minutes up to 2 hours
Auth unauthenticated, logged_out, api_mode outcomes generic backoff up to 30 minutes
Parked not_installed, unsupported_cli_version 6-hour backoff, released early when the CLI changes
Vendor drift vendor_drift fixed 1-hour backoff

Providers in active use refresh on the hot cadence instead of the idle one. A provider counts as hot while a recent agent launch or limit-event hint (each good for 15 minutes) is still live, or while one of its stored un-reset windows sits at or above warn_percent used. Hotness is suppressed while live stream events already keep every stored window fresh — a window received within the active cadence of now needs no probe. The hot interval is max(active_refresh_seconds, floor), so a hot Claude still waits for its 300 s floor while a hot Codex is due at about 120 s.

llm_provider:
  usage_metrics:
    enabled: true
    refresh_seconds: 300
    active_refresh_seconds: 120
    warn_percent: 75
    critical_percent: 90
    indicator:
      enabled: true
      default:
        below_remaining_percent: 20
      weekly_all: always
      providers:
        muse:
          windows:
            session: never
    providers:
      claude:
        enabled: true
Field Type Default Description
llm_provider.usage_metrics.enabled bool true Collect subscription usage. False stops probes, passive writes, scheduled requests, and attention; inspection can still explain the opt-out.
llm_provider.usage_metrics.refresh_seconds number 300 Idle refresh cadence in seconds. Must be finite and at least 60. The scheduler's usage routine ticks every 60 s and probes each provider at max(refresh_seconds, floor); display freshness uses the same max. The TUI header indicator uses the same max(refresh_seconds, floor) freshness as sase usage list and the Models panel, so a provider polled at its floor never shows as stale or unknown between probes. Explicit refreshes bypass the floor but still respect the 60 s cooldown and any Retry-After.
llm_provider.usage_metrics.active_refresh_seconds number 120 Hot refresh cadence in seconds for providers in active use. Must be finite and at least 60; values above refresh_seconds are capped at it. A hot provider probes at max(active_refresh_seconds, floor), so plugin floors still bound hot polling.
llm_provider.usage_metrics.warn_percent number 75 Percentage used that classifies a window as low. Must satisfy 0 <= warn_percent < critical_percent <= 100. UI copy uses percentage left.
llm_provider.usage_metrics.critical_percent number 90 Percentage used that classifies a window as very low.
llm_provider.usage_metrics.indicator.enabled bool true Show sase's TUI header usage indicators. False hides display entries while collection and Providers · Usage remain active.
llm_provider.usage_metrics.indicator.default policy {below_remaining_percent: 20} Fallback display policy for windows that do not match a more specific display override.
llm_provider.usage_metrics.indicator.weekly_all policy always Display policy for positively classified weekly all-model windows after exact-window and provider defaults.
llm_provider.usage_metrics.indicator.providers.<name>.default policy inherit Optional display policy for all observed windows from one provider. This map is separate from the sibling collection providers map.
llm_provider.usage_metrics.indicator.providers.<name>.windows.<key> policy inherit; Muse session never Exact provider-reported window-key override. Find keys with sase usage list -p <provider> --json at windows[].key.
llm_provider.usage_metrics.providers.<name>.enabled bool inherit Optional per-provider collection override. Keys are registered provider names; the generic schema does not hard-code any provider list.

An indicator policy is exactly one of:

  • always: display each matching observed window at any remaining percentage.
  • never: suppress matching display entries.
  • {below_remaining_percent: N}: display only when the unrounded remaining percentage is strictly less than finite N, where 0 <= N <= 100.

Thresholds use percentages remaining, not percentages used. 0 selects no numeric windows, 100 still excludes an exactly full window, and always is the only policy that includes full capacity.

Display policy precedence is exact window key, provider default, weekly_all for a positively classified weekly all-model window, then global indicator.default. Provider and window IDs are open-ended; unmatched future provider/window keys are inert.

The bundled default adds one exact Muse override:

llm_provider:
  usage_metrics:
    indicator:
      providers:
        muse:
          windows:
            session: never

Claude's observed weekly Fable window has no bundled entry, so the global indicator.default threshold governs it and the header shows it only when its remaining capacity is strictly below 20%. Set the exact key "weekly:claude-fable-5" to always to restore the always-visible behavior, to never to hide it even when low, or to a {below_remaining_percent: N} of its own. Missing or collection-ineligible Fable windows are not synthesized. Because config layers merge recursively, an empty user indicator.providers: {} does not erase this bundled key, while an explicit value for the key does override it. Broader provider/global defaults have lower selection precedence than the exact window key.

Muse reports two windows: a 5-hour session window and a weekly window. The header shows only the weekly one by default. Muse's weekly window is classified as a weekly all-model window, so the existing weekly_all: always policy selects it with no entry of its own, and it appears at any remaining percentage once Muse is an eligible provider (the muse CLI is installed and Muse is referenced by a model alias or enabled in llm_provider.usage_metrics.providers). The session: never override deliberately suppresses the 5-hour window even when it runs low; it stays visible in Providers · Usage and sase usage list -p muse. Restore header attention for it with:

llm_provider:
  usage_metrics:
    indicator:
      providers:
        muse:
          windows:
            session:
              below_remaining_percent: 20

Antigravity reports four windows — gemini-weekly, gemini-5h, 3p-weekly, and 3p-5h — and has no bundled indicator entry. Its Gemini weekly window is treated as the provider's weekly anchor (it matches the weekly_all policy while keeping its family:gemini scope), so weekly_all: always anchors it in the header unlabeled whenever agy is an eligible provider; a providers.agy.default policy would take precedence. The Gemini 5-hour window and both 3p-* (Claude/GPT) windows fall through to the global indicator.default threshold and appear only below 20% remaining. Pin or hide them with exact keys:

llm_provider:
  usage_metrics:
    indicator:
      providers:
        agy:
          windows:
            gemini-5h: always
            3p-weekly: never

Invalid indicator config is diagnosed with its config path, ignored at the smallest invalid override, and inherited/default policy is used instead of resetting unrelated valid usage settings. sase's TUI display settings are cached by the merged-config token, so config changes reload on the normal usage refresh cadence even when the usage-state file does not change.

Routing-disabled providers still refresh when collection is otherwise eligible, because reset information remains useful for them.

The same panel's fixed Ctrl+E binding manages the separate machine-wide default-effort override at ~/.sase/llm_effort_override.json. It uses the alias override duration and exact-time cards, but its state and precedence are independent: explicit prompt effort and alias/member effort win, then the temporary effort override, then llm_provider.default_effort, then the provider default. See Reasoning Effort.

Its fixed Ctrl+R binding manages max_running_agents: persistent edits target the user-base sase.yml (or its chezmoi source), while temporary values live independently in ~/.sase/max_running_agents_override.json. This is a Launch Control binding, not an ace.keymaps option.

llm_provider.continuation_budget

Byte budget for the continuation-budget preflight. Monitor successors always run it, and so does any invocation with SASE_CONTINUATION_BUDGET_ENFORCE=1. The preflight measures the final expanded prompt; when its essential content cannot fit the effective budget, SASE records the decision and refuses before calling the provider.

llm_provider:
  continuation_budget:
    context_limit_bytes: 800000
    estimate_uncertain: true
    instruction_reserve_bytes: 0
    tool_reserve_bytes: 0
    output_reserve_bytes: 0
    reasoning_reserve_bytes: 0
    providers:
      agy:
        transport_limit_bytes: 122880
        instruction_reserve_bytes: 512
        # models:
        #   <model-name>: { context_limit_bytes: 400000 }
Field Type Default Minimum Description
context_limit_bytes int 800000 1 Provider context budget, in UTF-8 bytes, before reserves are subtracted.
transport_limit_bytes int unset 1 Largest prompt the provider transport can carry safely, such as a single-argv limit.
checkpoint_threshold_bytes int unset 1 Prompt size at which checkpoint-backed reductions are considered.
instruction_reserve_bytes int 0 0 Bytes reserved for provider-side system instructions or command framing.
tool_reserve_bytes int 0 0 Bytes reserved for provider tool definitions or tool-call framing.
output_reserve_bytes int 0 0 Bytes reserved for the model's response.
reasoning_reserve_bytes int 0 0 Bytes reserved for hidden reasoning or other provider overhead.
estimate_uncertain bool true - Mark byte estimates as uncertain when exact provider accounting is unavailable.
providers.<name> dict - - Overrides of any field above for one registered provider.
providers.<name>.models dict - - Overrides keyed by provider-resolved model name, applied on top of that provider's entry.

Each field resolves independently: a matching model entry wins over its provider entry, which wins over the shared values, and the matching SASE_CONTINUATION_* variable (see LLM Provider environment variables) outranks all of them. A zero, negative, or non-integer value uses the built-in default instead. The bundled agy entry reflects that Antigravity sends the print prompt as one argv element; SASE keeps those agy transport and instruction-reserve values even without configuration and never lets the agy instruction reserve drop below 512 bytes.

Source: src/sase/llm_provider/continuation_budget_request.py

llm_provider.retry

Per-provider retry and fallback configuration. See docs/llms.md for the full retry flow and TUI display.

llm_provider:
  retry:
    claude:
      max_retries: 3
      error_patterns:
        - "API Error: 500"
      wait_times: [60, 300, 1800]
      fallback_model: "sonnet"
      continuation_prompt: "Please continue from the last preserved work."
      preserve_workspace: true
      spawn_new_agent: false
Field Type Default Description
llm_provider.retry.<provider> dict - Retry config for a specific provider (e.g., agy, claude, codex).
llm_provider.retry.<provider>.max_retries int 0 Maximum retry attempts. 0 disables retrying.
llm_provider.retry.<provider>.error_patterns list [] Case-insensitive substring patterns matched against error output.
llm_provider.retry.<provider>.wait_times list [30] Per-retry wait times in seconds. Last value reused if list is shorter.
llm_provider.retry.<provider>.fallback_model str null Alternate model to use after exhausting all retries.
llm_provider.retry.<provider>.continuation_prompt str null Prompt text prepended when continuing after a retryable failure.
llm_provider.retry.<provider>.preserve_workspace bool false Preserve on-disk edits across legacy in-process retry attempts.
llm_provider.retry.<provider>.spawn_new_agent bool false Retry by launching a fresh detached agent that inherits the workspace.

Configured retry policy is merged with provider-supplied retry defaults when a provider declares them. For list fields such as error_patterns, built-in patterns are kept and configured patterns are appended with duplicates removed. Claude's provider hook adds workspace-preserving matching for context-limit, socket-close, and Claude CLI API-error output, plus a continuation nudge. Those hook defaults are merged with the bundled Claude policy in default_config.yml, so the configured wait times and fallback model still apply unless you override them.

Source: src/sase/llm_provider/retry_config.py, src/sase/llm_provider/config.py

finalizers

Configures host-owned completion finalizers for SASE-launched agent runs. The bundled default selects the built-in commit instance, preserving ordinary commit enforcement without runtime-specific hooks.

finalizers:
  defaults: [commit]
  required: []
  instances:
    commit:
      use: builtin@commit
      after: []
      max_attempts: 2
      refusal: defer

defaults is the ordered selection used when a prompt omits %final. required instances cannot be removed by %final:none or %final:!name. instances is keyed by lowercase slug; each instance names a trusted provider with use, optional dependency edges in after, bounded attempts through max_attempts, refusal handling, and provider-specific config.

sase final submit is authoring-only: every dirty repository obligation takes a Conventional Commit message, because SASE agents work in ephemeral numbered workspace clones where uncommitted work is lost work. There is no free-text refusal action. The only escape hatch is sase final defer, a separate, deliberate command that names a typed reason from a closed set (protected_paths, foreign_work, unsafe_content, belongs_to_another_turn) and is adjudicated by the host against run evidence at submit time — a deferral the host can disprove is rejected with counter-evidence so the agent can repair and resubmit in the same turn. refusal controls what happens when the host upholds a deferral: fail (still fails the run; the historical behavior) or defer (the shipped default — the run completes with a distinct deferred status and the tree stays dirty, and a notification names the repository, reason, paths, and the command to finish the commit by hand).

Only trusted configuration can define providers, commands, cwd policy, environment allowlists, timeouts, or retry policy. Prompt text can only select configured instances: %final:lint adds lint, %final:none clears defaults unless blocked by required, and repeated/comma-separated %final operations replay left to right before dependency ordering. Plugin packages may advertise providers through the sase_finalizers entry-point group, but installation alone is inert; a trusted config layer must declare and select an instance. Plugin-contributed config layers cannot activate finalizers.

Use sase final list, sase final show <instance>, and sase final doctor to inspect effective policy, provider provenance, and diagnostics. During an active agent turn, generated agent instructions tell the model to finish with /sase_final. That skill uses sase final context -f json and sase final submit to publish the one turn-bound declaration required by selected finalizers, and it returns after reading context when no payload is required. If a required submission is missing or stale, the host opens one bounded recovery turn that explicitly requests /sase_final. The host executes and verifies finalizers after the model returns.

sase final prepare <wrapper.json> is the conditional alternative for a long verification handoff. It seals a completed declaration, exact verification argv, current context, and repository observations without submitting or executing them. Bind the returned single-use ref with sase monitor start -f <ref>; only an eligible successful monitor result lets the host install that declaration and run finalizers without another model turn. A plain monitor profile carries no completion authority. See Prepared host completion.

The built-in builtin@commit provider checks the main workspace, configured linked Git worktrees, and repositories opened through /sase_repo. Dirty enforced repositories become declaration obligations; each repository must receive exactly one commit decision with a Conventional Commit message, and commit is the only legal repository action. Typed deferrals can name explicit paths that must not be committed, using the adjudicated reasons protected_paths, foreign_work, unsafe_content, or belongs_to_another_turn. When the run has an assigned bead (SASE_BEAD_ID), the primary repository's commit decision also carries a keep or close bead action, which is forwarded to sase stitch create -B; see Explicit Bead Action. Commit decisions dispatch through sase stitch create sequentially, preserve protected pre-existing dirt, write stitch evidence, stop on the first conflict for repair/resume, and fail completion when a rejected deferral or unrepaired dirty state remains. Later mutating finalizers can reactivate commit until the controller reaches its bounded fixed point.

When $SASE_ARTIFACTS_DIR is set, new runs write generic finalizer artifacts: finalizer_baseline.json, final_context.json, final_submission.json, final_submission_attempts.jsonl, finalizer_result.json, and per-instance files under finalizers/<instance>/. The commit provider also keeps current stitch evidence in commit_results.json. Historical commit_finalizer_* artifacts are read only for archived run reporting and are not a supported output format for new runs.

commit.message

Configures the Conventional Commit subject gate that sase stitch create applies to every create_commit, create_proposal, and create_pull_request message before any side effect runs.

commit:
  message:
    require_conventional_subject: true
    allowed_types:
      [build, chore, ci, deps, docs, feat, fix, perf, refactor, revert, style, test]
Field Type Default Description
commit.message.require_conventional_subject bool true Reject a sase stitch create message whose subject line is not a Conventional Commit.
commit.message.allowed_types list the 12 types above Commit types this project accepts. A configured list replaces the built-in set.

The subject must match <type>[(<scope>)][!]: <description>. The scope is optional, one or more spaces may follow the colon, and no length or capitalization rule is applied to the description. The type itself must be lowercase, because release tooling does not classify capitalized types reliably. Only the first line is inspected; nothing below the subject is validated.

Subjects beginning with Merge, Revert ", fixup!, squash!, or amend! are exempt and always pass — these are mechanical git-generated or rebase-directive subjects. An empty message is always rejected.

A rejection fails the workflow before beads are closed, plans are staged, or the before-commit hook runs, and the -M message file is preserved so the same command can be re-run after the subject is rewritten. There is no per-invocation bypass flag or environment variable; a project that does not use Conventional Commits sets require_conventional_subject: false.

Source: src/sase/finalizers/controller.py, src/sase/finalizers/commit.py, src/sase/commit_instructions.py, src/sase/workflows/commit/message_validation.py, src/sase/core/commit_subject_facade.py

monitor

Controls which monitors run their command inside sase tool run and the amount of command evidence projected into a monitor continuation prompt. The evidence limits do not change the monitor's bounded rotating log or the output available to sase monitor show; they bound only automatically selected prompt content.

monitor:
  tool_wrap: verify
  evidence_limits:
    selected_diagnostics_bytes: 8192
    fallback_tail_bytes: 4096
    total_raw_excerpt_bytes: 12288
    raw_tail_lines: 200
Field Type Default Minimum Description
monitor.tool_wrap off, verify, or all verify - Which monitors run inside sase tool run: verify wraps only -p verify monitors, all wraps every monitor, off wraps none.
monitor.evidence_limits.selected_diagnostics_bytes int 8192 1 UTF-8 bytes of selected failed-stage diagnostics eligible for embedding.
monitor.evidence_limits.fallback_tail_bytes int 4096 1 UTF-8 bytes of fallback raw tail when diagnostics are unavailable.
monitor.evidence_limits.total_raw_excerpt_bytes int 12288 1 Aggregate raw-evidence byte cap for one continuation prompt.
monitor.evidence_limits.raw_tail_lines int 200 1 Retained-output lines considered when selecting raw-tail evidence.

selected_diagnostics_bytes and fallback_tail_bytes must each be no larger than total_raw_excerpt_bytes. The runtime falls back to all four packaged defaults if that relationship is invalid; schema validation rejects non-positive values. Strict --next-output file and none policies still embed no raw command output.

An unrecognized tool_wrap value falls back to verify at runtime. Wrapping changes only the argv the proc supervisor execs, never the recorded monitor command; see Tool-run wrapping for the per-command rules.

Source: src/sase/default_config.yml, src/sase/config/_settings_display.py, src/sase/config/sase.schema.json, src/sase/monitor/result_projection.py, src/sase/monitor/tool_wrap.py

repos

Declares linked and sidecar repositories related to a project. Git linked-repo worktrees are eligible for commit-finalizer checks at their resolved workspace_dir. Agents use /sase_repo to prepare them; its audited sase repo open command records manually opened linked workspaces in run artifacts for sase's TUI context and appends a durable audit event. SASE materializes a hidden sibling-state ProjectSpec for the linked repo when needed. Entries can live in user config or a project-local sase/sase.yml; local entries are resolved relative to the project's primary workspace directory.

Linked repositories are lazy by default. Set auto_clone: true for a repository that every launched agent needs; SASE materializes and prepares those entries before execution. Lazy entries remain available through sase repo open, but their per-repository *_DIR environment variables are not exported until the clone exists. Repositories with auto_clone: true are omitted from generated agent instructions because agents do not need to open them manually.

auto_clone and auto_sync are independent settings on a sidecar entry. auto_clone controls whether a numbered workspace materializes its own clone of the sidecar before each agent launch; that clone is disposable and owned by the launched agent's workspace. auto_sync instead opts the primary checkout's already-materialized sidecar clone into conservative background convergence: SASE fetches and fast-forwards it only while it is clean, attached, and non-diverged, and never rebases, commits, hard-resets, or removes user state. A dirty, detached, diverged, remote-mismatched, or not-yet-materialized primary sidecar clone is left untouched and reported rather than repaired. Managed projects enable auto_sync for the generated plans, beads, and research entries; custom roles default to false and opt in with the same property. See Ownership Boundary for the underlying primary/leased distinction and how sync is scheduled.

repos.sidecar is a two-bucket mapping keyed by role: builtin holds overrides of the reserved plans, beads, agents, attachments, and attachments-private roles, and custom holds user-declared document sidecars such as research. The map key is the role, so an entry never carries a name field and a role cannot be declared twice in one bucket. Because both buckets are mappings, a later config layer merges into an inherited entry per key — a project-local custom: {research: {disabled: true}} opts out of a global research sidecar. The former list form (a sequence of entries each carrying name) is no longer accepted and is ignored; run sase doctor to see which bucket each stale entry belongs in.

Sidecar entries use their role key as the primary CLI lookup key. Ordinary roles use sase/repos/<role> as their workspace clone directory. Their repository defaults to <project>--<role> in the primary repository's GitHub organization; repo can pin a bare slug or owner/repo. An explicit unpinned entry uses that project-local derivation even when a legacy SDD store record names a different repository. Configured sidecars appear in sase repo list even before cloning and can be opened by role name or repository slug. Enabled ordinary sidecars that are not auto-cloned also appear by repository slug in generated agent instruction files, where their description tells agents when to open them with /sase_repo. Set disabled: true in a later config layer to suppress a matching global entry or implicit fallback; disabled and auto-cloned sidecars are omitted from generated instructions.

The roles plans, beads, agents, attachments, and attachments-private are reserved and are configured under repos.sidecar.builtin. plans owns canonical plans, beads owns the event store, agents is the hidden machine-level publication store plus the canonical prompt and prompt-artifact archive, attachments is the hidden bare store for public bead attachment bytes, and attachments-private is the hidden bare store for private bead attachment bytes. Every other enabled role lives under repos.sidecar.custom and is a document sidecar: a <YYYYMM>/*.md corpus whose kind label is the role name. attachments and attachments-private are not document roles, so neither can be declared under custom. Document roles receive clone/store resolution, sase repo path <role>, doctor validation, commit routing, SASE_SDD_<ROLE>_DIR, plan-search visibility, and an sase's TUI Plans kind. research is simply the default-seeded document role; only its illustrated README/directory-map preset is name-specific.

The agents role is intrinsically hidden from agent workflows. It never appears in generated memory, launch metadata, linked-repository environment variables, or a workspace's sase/repos/ tree, even if an override sets auto_clone: true. It remains visible to users as a sidecar row in sase repo list, and sase repo path agents or sase repo open agents -r "<reason>" explicitly accesses the one machine-level clone at ~/.sase/projects/<project_key>/repos/agents. The derived or pinned repository slug is also accepted by those commands.

attachments-private is also hidden from agent instructions. Managed projects receive an implicit private <project>--attachments-private entry unless configuration names that role or default_linked_repos is false. Its clone is a bare partial repository (git clone --bare --filter=blob:none) at ~/.sase/projects/<project_key>/repos/attachments-private, with no worktree and no README. sase repo list shows the role, and sase repo path attachments-private prints that path. auto_clone: true does not put a copy in a workspace's sase/repos/ tree. Resolved visibility is always private: a visibility: public override is rewritten to private, and preflight rejects a provider that would create the remote with another visibility. SASE does not verify the visibility of a remote that already exists. Opt out of sidecar setup with repos.sidecar.builtin.attachments-private.disabled: true. Attachment commands still use an existing local clone even when that role is disabled; -L/--local-only keeps an individual attachment on this machine. When available, the git store takes objects up to bead.attachments.git_max_bytes; see Attachments.

The beads role does not default to auto_clone: true: the beads sidecar materializes on demand, in sase bead and in the agent-launch bead claim, instead of during workspace preparation. It is unconditionally omitted from generated agent instructions regardless of its auto_clone setting, the same way agents is, because agents reach bead state through sase bead rather than /sase_repo. Unlike agents, it otherwise behaves like an ordinary reserved sidecar: it appears in sase repo list and resolves through sase repo path beads or sase repo open beads -r "<reason>".

The workspace provider owns sidecar transport. GitHub sidecars use canonical SSH origins on the primary repository's GitHub host (git@host:owner/repo.git, or ssh://git@host:port/owner/repo.git when a port is configured). Read-only store resolution converts a legacy GitHub HTTPS record to that exact SSH form in memory, so inventory, launch-time auto-cloning, and on-demand materialization are safe immediately without rewriting the durable record. Matching retained HTTPS clones keep their checkout and local state while SASE rewrites origin in place. Any HTTP(S) sidecar remote that cannot be derived from consistent GitHub provider, host, and repository metadata fails materialization before Git runs. Rerun sase repo init to persist the migrated record; it is not required to make a launch safe.

Managed projects (is_sase_managed: true) receive deterministic <project>--plans (auto_clone: true), <project>--beads (auto_clone: false), <project>--agents (auto_clone: false, public visibility), and private <project>--attachments-private entries when no matching explicit sidecar is configured. Research is config-declared per project and defaults to <owner>/<project>--research; sase repo init writes the plans and research entries. A project-local agents or attachments-private entry replaces that implicit entry. For agents, use disabled: true to opt out or visibility: private to retain a private remote. For attachments-private, use disabled: true to opt out; visibility stays private. Project-local default_linked_repos: false suppresses those implicit managed-project entries. sase repo init can create and seed the agents remote only after its separate default-no consent prompt. Successful agent commit/PR workflows publish the committing hood, while sase agent sync publishes every locally commit-eligible hood through the stable machine-level clone. See Agent Hood Synchronization before enabling a public remote.

The deprecated linked_repos and sibling_repos keys are still accepted as aliases during the compatibility window. Canonical repos.linked entries take precedence over both aliases when the same name is defined.

A linked entry may declare revision_pin, a pin file relative to the primary checkout root that holds that repo's pushed SHA (for example sase-core-revision.txt). The path must be relative and stay inside the checkout. Absolute paths, ~, drive-letter paths, and .. that would leave the checkout are rejected when the config is read. A relative path that still leaves through a symlinked parent directory or a symlinked pin file is not followed: sase doctor reports that escape, and the commit finalizer skips the pin write with pin-escapes-checkout. sase doctor also flags a pin file that is missing or does not hold a 40-character hex SHA. It resolves the pin from the current working directory, so run it from the primary checkout. When one accepted declaration commits both the primary and a pinned sibling, the host commits the sibling first and writes its pushed SHA into the pin file before the primary commit. A skipped pin records the reason and does not fail the run.

github_orgs:
  - sase-org
repos:
  linked:
    - name: core
      path: ../sase-core
      description: Shared backend/domain behavior used by SASE frontends.
      auto_clone: true
  sidecar:
    builtin:
      plans:
        auto_clone: true
        ref:
          use: builtin@plan
      agents:
        visibility: private
    custom:
      research:
        description: Durable SASE research reports and generated media.
        visibility: public
        ref:
          icon: ∴
          inventory:
            globs: ["reports/**/*.md", "!drafts/**"]
Field Type Default Description
github_orgs string or list - GitHub user/org namespaces available to provider completion and PR workflows.
default_linked_repos boolean true Inject managed-project --plans, --beads, and hidden --agents, --attachments, and --attachments-private sidecars.
repos.linked[].auto_clone boolean false Materialize and prepare the repository automatically before each agent launch.
repos.linked[].name string required Stable alias used in generated environment variable names and memory summaries.
repos.linked[].path string required Primary checkout path. Relative paths resolve from the project's primary workspace.
repos.linked[].description string required Human-readable purpose used when generating agent memory for the linked repository.
repos.linked[].revision_pin string - Pin file relative to the primary checkout root holding this repo's pushed SHA. The commit finalizer commits the pinned sibling first and writes its SHA into the pin file before the primary commit. The path must stay inside the checkout, including through symlinks.
repos.sidecar.builtin.<role> object - Override for a reserved role; the key must be plans, beads, agents, attachments, or attachments-private.
repos.sidecar.custom.<role> object - User-declared document sidecar; the key is the role and must not be a reserved one (plans, beads, agents, attachments, attachments-private).
repos.sidecar.*.<role>.repo string derived Optional bare slug or owner/repo pin.
repos.sidecar.*.<role>.description string - Purpose shown in inventory; required in generated instructions for lazy entries.
repos.sidecar.*.<role>.auto_clone boolean false Materialize before agent launch; intrinsically ignored for the hidden agents and attachments-private roles.
repos.sidecar.*.<role>.auto_sync boolean false Fetch/fast-forward the primary clone when clean; intrinsically ignored for the hidden agents and attachments-private roles.
repos.sidecar.*.<role>.visibility public/private public Requested visibility for remote creation; project-local private overrides the agents default. attachments resolves to public and attachments-private resolves to private.
repos.sidecar.*.<role>.disabled boolean false Disable the entry and suppress matching implicit sidecars, including agents.
repos.sidecar.*.<role>.ref.use string role/provider dependent Installed artifact-reference provider, qualified <plugin>@<id>, to use as the base policy.
repos.sidecar.*.<role>.ref.kind string role name (plan for plans) Prompt kind exposed as @<kind>:<path>.
repos.sidecar.*.<role>.ref.icon string role/provider dependent Artifacts tab mark shown beside the pane label.
repos.sidecar.*.<role>.ref.expansion_format string the {repo_relative_path} file in the {sidecar_role} sidecar repo Provider expansion format; see Expansion.
repos.sidecar.*.<role>.ref.properties object {} Typed metadata fields extracted by the provider.
repos.sidecar.*.<role>.ref.detail object {} Metadata fields shown by completion and detail surfaces.
repos.sidecar.*.<role>.ref.identity object {} Optional provider identity rule.
repos.sidecar.*.<role>.ref.inventory.globs list[string] ["**/*.md"] for document sidecars Repo-relative POSIX includes and ! exclusions.
repos.sidecar.*.<role>.ref.publication object VCS permalink / Markdown references Publication link and reverse-reference policy.

Every enabled document sidecar exposes one compact @<kind>:<path> reference. Plans use the built-in plan provider, and sase repo init records ref: {use: builtin@plan}. Other document roles default to their role name, the sidecar pointer format the {repo_relative_path} file in the {sidecar_role} sidecar repo, and **/*.md inventory even when ref is omitted. Explicit custom path-bound providers may opt into the {checkout_path} file.

ref.use selects a declarative provider installed through the sase_artifact_refs plugin entry-point group, qualified as <plugin>@<id> where <plugin> is the literal builtin or the installed distribution name. Any sibling keys deep-merge over that provider's base spec. A cloned sidecar does not install its provider: when use names an unavailable provider or omits its plugin prefix, the role's reference policy is disabled and sase doctor -C config.repos reports how to fix it. Authors may omit use and provide the inline fields in the table instead.

ref.macro is retired and invalid. ref.filters.path_globs remains a deprecated alias that warns and maps to ref.inventory.globs; new configuration must use inventory.globs. The beads and agents sidecars are entity-backed rather than document inventories, so document filters do not apply to them. See Artifact References for canonical prompt forms and project-context rules.

Workspace numbers 0 and 1 use the linked repo's primary checkout. Higher workspace numbers use <host_workspace>/sase/repos/linked/<linked_repo>, naturally namespaced by host project and workspace number. Agent and workflow launch preparation atomically removes the numbered checkout's entire <host_workspace>/sase/repos/ tree. The required plans sidecar is then cloned directly from the canonical SSH or local remote resolved from its recorded metadata; other linked repositories and sidecars remain lazy unless configured with auto_clone: true. Legacy GitHub HTTPS metadata is normalized before the clone command is built, and unresolved HTTP(S) metadata stops launch setup before Git executes. The hidden agents sidecar is excluded from this clone mapping and always resolves every registered workspace number to ~/.sase/projects/<project_key>/repos/agents. Agents materialize ordinary lazy entries on demand through /sase_repo. sase repo init manages the tracked /sase/repos/ ignore rule, while SASE also installs the rule in .git/info/exclude before materialization. SASE passes resolved metadata for all entries and exports per-repository paths only for materialized entries:

Variable Description
SASE_LINKED_REPOS_JSON JSON metadata for all resolved linked repos.
SASE_LINKED_REPO_<ENV_NAME>_DIR Workspace-matched directory for a linked repo.
SASE_LINKED_REPO_<ENV_NAME>_PRIMARY_DIR Primary checkout directory for that linked repo.

The legacy SASE_SIBLING_REPOS_JSON and SASE_SIBLING_REPO_<ENV_NAME>_* variables are still emitted alongside the canonical ones during the compatibility window.

<ENV_NAME> is the uppercased, sanitized repo name; duplicates are uniquified with a numeric suffix.

Source: src/sase/linked_repos.py, src/sase/agent/launch_spawn.py

External repositories

External repositories are per-task repos that are not part of the host project's configured inventory. They require no configuration entry. sase repo open resolves them after inventory names in two forms:

  • Another registered SASE project name opens that project's primary repo from its local checkout, without network access, under sase/repos/external/projects/<project>.
  • gh:owner/repo, or the owner/repo shorthand, clones through the installed GitHub workspace provider under sase/repos/external/gh/<owner>/<repo>.

Successful external opens are idempotent, audited, and included in sase repo list, commit-finalizer enforcement, sase's TUI file and commit deltas, and revert. Agents must use /sase_repo before reading or modifying any external repo and must use the path printed by the skill rather than locating or cloning the repo themselves. External repos are workspace-local and do not create project registry records.

dispatch

Configures remote-machine discovery, enrollment records, gateway deadlines, and the local federation worker. Ordinary config loading is pure: it reads and validates this section but never discovers peers or contacts a gateway. Use sase machine init, discover, or status for explicit network work. See the Remote Dispatch Runbook for the supported setup flow.

dispatch:
  providers:
    builtin@https:
      enabled: true
    builtin@tailnet:
      enabled: true
  machines: {}
  discovery:
    enabled_providers: [builtin@tailnet]
  request_timeout_seconds: 5
  status_cache_seconds: 60
  federation_worker:
    enabled: true
    command: ""
    sase_home: ""
    run_root: ""
    socket_path: ""
    idle_timeout_seconds: 300
    startup_timeout_seconds: 5
    request_timeout_seconds: 5
    max_frame_bytes: 1048576
  remote_hosts: []
Field Type Default Description
dispatch.providers.<ref>.enabled bool provider-defined Enable one provider for explicit dispatch operations. Built-in HTTPS and tailnet providers ship enabled.
dispatch.machines map {} Viewer-local enrolled aliases. Prefer sase machine init, add, repair, rename, and remove over hand edits.
dispatch.discovery.enabled_providers list[string] [builtin@tailnet] Provider refs queried by explicit discovery.
dispatch.request_timeout_seconds number 5 Positive default deadline for fleet gateway calls.
dispatch.status_cache_seconds number 60 Freshness window reserved for dispatch status caches.
dispatch.federation_worker.enabled bool true Allow the local worker to start when enrolled machines exist.
dispatch.federation_worker.command string "" Worker argv override, parsed without a shell; empty discovers the packaged or linked-development worker.
dispatch.federation_worker.sase_home string "" State root passed to the worker; empty uses the runtime default.
dispatch.federation_worker.run_root string "" Host-local runtime directory; empty derives it from SASE home and host identity.
dispatch.federation_worker.socket_path string "" Unix-socket override; empty derives it from the runtime directory.
dispatch.federation_worker.idle_timeout_seconds number 300 Positive idle time before an unused worker may exit.
dispatch.federation_worker.startup_timeout_seconds number 5 Positive deadline for a newly spawned worker to answer IPC health.
dispatch.federation_worker.request_timeout_seconds number 5 Positive default deadline for worker facade calls.
dispatch.federation_worker.max_frame_bytes int 1048576 Maximum IPC request or response frame size; minimum 128 bytes.
dispatch.remote_hosts list [] Deprecated legacy host list. When non-empty, federation uses it instead of dispatch.machines; do not mix the forms.

Machine aliases are 1–64 characters, must start with an ASCII letter or digit, and may otherwise contain ASCII letters, digits, _, ., and -. Each machine record stores a dispatch provider ref, HTTPS endpoint, opaque credential reference, and pinned sase_inst_v1_... installation identity. Optional ssh_target stores the SSH destination used for terminal handoffs; when omitted it defaults to the alias at use time. Optional fields select gateway, tunnel, or direct connection kind; TLS trust mode (system_roots, pinned_ca, or pinned_server_name); and quarantine state. Tokens do not belong in YAML: credential_ref points into the protected local credential store.

Enrollment commands write those records and keep the installation pin authoritative. A quarantined alias cannot receive launches or lifecycle mutations until repaired.

Source: src/sase/dispatch/config.py, src/sase/dispatch/models.py, src/sase/config/sase.schema.json

vcs_provider

Configures the version control system backend. See docs/vcs.md for the full VCS provider reference including per-command behavior, Git/Mercurial details, and troubleshooting.

GitHub Enterprise host configuration (github_hosts) is owned by the sase-github plugin; see its GitHub Enterprise setup walkthrough.

vcs_provider:
  provider: auto # "git", "hg", or "auto" (default: "auto")
  workspace_root: ~/workspace # optional workspace root directory
  default_hooks: # optional list overriding built-in default hooks
    - "!$my_presubmit"
    - "$my_lint"
  pr_tags: # optional key-value tags appended to PR commit messages (keys are rendered SASE_-prefixed, e.g. SASE_BUG)
    BUG: "b/12345"
  use_project_pr_prefix: false # prepend [<project>] to PR titles (default: false)
Field Type Default Description
vcs_provider.provider string "auto" VCS provider: "git", "hg", or "auto" for directory detection.
vcs_provider.workspace_root string - Legacy VCS helper workspace root. New numbered-checkout layout is configured by workspace.root below.
vcs_provider.default_hooks list[string] - Hook commands added to new Patches. Replaces built-in defaults.
vcs_provider.pr_tags dict[string, str] {} Key-value tags appended as SASE_TAG=VALUE lines to PR commit messages (keys are rendered SASE_-prefixed).
vcs_provider.use_project_pr_prefix bool false Prepend [<project>] to PR titles / PR descriptions (see below).

When default_hooks is not set, plugins may provide their own defaults via default_config.yml (for example, Mercurial-specific hooks from a provider plugin). The core sase package has no built-in default hooks.

When use_project_pr_prefix is true, a [<project>] prefix is prepended to PR titles (GitHub) or PR descriptions (Mercurial) without polluting the Patch DESCRIPTION or git commit message. The prefix is automatically stripped when reading descriptions back.

Source: src/sase/vcs_provider/config.py, src/sase/ace/hooks/defaults.py

vcs_repo_completion

Configures repository-name completion inside VCS workflow refs such as #gh:owner/.

vcs_repo_completion:
  enabled: true
  cache_ttl_seconds: 600
  max_repos: 200
Field Type Default Description
vcs_repo_completion.enabled bool true Enable sase's TUI and helper-bridge repository completion for registered VCS workflow refs.
vcs_repo_completion.cache_ttl_seconds int 600 Freshness window for the shared on-disk repository candidate cache, in seconds.
vcs_repo_completion.max_repos int 200 Maximum repository candidates kept from a provider response and returned to completion UIs.

When disabled, sase's TUI does not detect repository-completion triggers, and the editor helper bridge returns an empty catalog. Repository candidates are listed through workspace-provider hooks, so provider-specific authentication and network requirements belong to the installed plugin. For GitHub, the sase-github plugin uses the gh CLI and can return private repositories visible to the authenticated user.

Source: src/sase/default_config.yml, src/sase/macro/vcs_repo_completion.py

vcs_ref_completion

Configures project, Patch, and namespace completion at the root of VCS workflow refs such as #gh: and #git:.

vcs_ref_completion:
  enabled: true
Field Type Default Description
vcs_ref_completion.enabled bool true Enable sase's TUI and macro LSP completion at the root of VCS workflow refs.

When disabled, sase's TUI does not detect VCS ref-root completion triggers and the materialized macro LSP VCS catalog omits namespace rows. Project and Patch candidates come from local ProjectSpecs; provider namespace rows come from fast local workspace-provider hooks.

Source: src/sase/default_config.yml, src/sase/macro/vcs_ref_completion.py

axe

Configures the scheduler's routine-based automation. The scheduler architecture uses an orchestrator that spawns multiple routines, each running a set of jobs on a fixed interval. Defaults are provided by src/sase/default_config.yml. The config section keeps its historical axe name.

The YAML below is an abridged illustration of the shipped defaults, not the whole file: it shows the shape of a lane and a job and omits some lanes and jobs entirely. See AXE Automation for the complete lane-by-lane inventory, and src/sase/default_config.yml for the literal defaults.

axe:
  max_hook_runners: 3 # concurrent hook runners (default: 3)
  max_agent_runners: 3 # concurrent agent runners (default: 3)
  zombie_timeout_seconds: 7200 # seconds (default: 7200 = 2 hours)
  query: "" # query filter for Patches (default: all)
  job_script_dirs: [] # additional directories to search for job scripts
  routines:
    hooks:
      description: |-
        Fast lane that advances hook, mentor, and workflow lifecycle state every few seconds

        Runs every five seconds with a 90-second per-job timeout so completed work is noticed and new work starts
        promptly. Put latency-sensitive Patch lifecycle reconciliation here; slower remote polling, wait
        coordination, and maintenance belong in the other lanes.
      interval: 5
      job_timeout: "90s"
      jobs:
        - name: hook_checks
          script: sase_job_hook_checks
          trigger:
            provider: fs
            max_quiet: "120s"
            paths:
              - path: projects
                glob: "*/*.sase"
              - path: projects
                glob: "*/*.gp"
          description: |-
            Complete finished hooks and start stale ones, with zombie detection

            Scans hook entries on every matching Patch, records completed process results, and starts stale hooks
            when a runner slot is free. Honors max_hook_runners across the tick; stale fix-hook suffixes older than
            zombie_timeout_seconds become ZOMBIE, while terminal Patches may finish hooks but cannot start new ones.
        - name: mentor_checks
          script: sase_job_mentor_checks
          trigger:
            provider: fs
            max_quiet: "120s"
            paths:
              - path: projects
                glob: "*/*.sase"
              - path: projects
                glob: "*/*.gp"
          description: |-
            Start mentor workflows once all hook prerequisites are met

            Reconciles running mentors, stops mentors left behind by older commits, adds matching mentor profiles, and
            launches ready profiles after their hooks finish. Mentor launches share max_agent_runners with other agent
            workflows, and review-ineligible or terminal Patches are skipped.
        - name: workflow_checks
          script: sase_job_workflow_checks
          trigger:
            provider: fs
            max_quiet: "120s"
            paths:
              - path: projects
                glob: "*/*.sase"
              - path: projects
                glob: "*/*.gp"
          description: |-
            Complete finished CRS/fix-hook workflows and start stale ones

            Reads workflow state from every matching Patch, records results for finished CRS and fix-hook agents,
            and launches stale workflows. New workflows share max_agent_runners and the current tick's agent-launch
            budget with mentors, so a full runner pool defers work instead of queueing it.
        - name: pending_checks_poll
          script: sase_job_pending_checks_poll
          trigger:
            provider: fs
            max_quiet: "120s"
            paths:
              - path: checks
                glob: "*/*.txt"
          description: |-
            Poll background is_cl_submitted and critique_comments checks for results

            Scans the pending-check directory once per tick, applies completed results to matching Patches, and
            reaps output files orphaned by killed or crashed checks. This job only consumes background results;
            pr_submitted_checks and comment_checks launch the remote checks.
        - name: comment_zombie_checks
          script: sase_job_comment_zombie_checks
          trigger:
            provider: fs
            max_quiet: "120s"
            paths:
              - path: projects
                glob: "*/*.sase"
              - path: projects
                glob: "*/*.gp"
          description: |-
            Mark comment threads older than zombie_timeout as ZOMBIE

            Examines comment-entry suffix timestamps on matching Patches and writes a ZOMBIE suffix when an entry
            exceeds zombie_timeout_seconds. It performs no remote comment fetch; comment_checks starts those checks and
            pending_checks_poll applies their results.
        - name: suffix_transforms
          script: sase_job_suffix_transforms
          trigger:
            provider: fs
            max_quiet: "120s"
            paths:
              - path: projects
                glob: "*/*.sase"
              - path: projects
                glob: "*/*.gp"
          description: |-
            Strip stale suffixes from older proposals and update mail-readiness markers

            Normalizes matching Patches in place by converting old proposal markers from !: to ~:, removing error
            markers from superseded stitches, and acknowledging attention markers on terminal statuses. It only
            repairs stored suffix state and never launches hooks or agents.
        - name: orphan_cleanup
          script: sase_job_orphan_cleanup
          trigger:
            provider: fs
            max_quiet: "120s"
            paths:
              - path: projects
                glob: "*/*.sase"
              - path: projects
                glob: "*/*.gp"
          description: |-
            Release workspace claims orphaned by reverted PRs with dead PIDs

            Reads all Patches and workspace claims, regardless of the axe query, then releases unpinned claims tied
            to Reverted Patches when their owning PID is absent or dead. Live claims and pinned workspaces are left
            untouched.
        # stale_running_cleanup keeps the default `always` trigger: dead-PID detection
        # has no filesystem proxy to watch.
        - name: stale_running_cleanup
          script: sase_job_stale_running_cleanup
          description: |-
            Release workspace claims and terminalize proc rows held by dead processes

            Walks every project, including disabled projects, and releases unpinned workspace claims whose owning
            process has exited. A pinned held claim is preserved while its agent artifacts still exist; this fast-lane
            placement frees ordinary dead claims within seconds. It also terminalizes active proc rows whose supervisor
            exited without reporting.
    waits:
      description: |-
        Reconcile bead claims and flush orphaned epic launches

        Runs every ten seconds so short-lived bead claims are reconciled quickly. Agent dependency resolution
        lives in the agent_waits lane and sidecar syncing in the sidecar_sync lane; Patch lifecycle checks and
        general cleanup belong in their dedicated lanes.
      interval: 10
      jobs:
        - name: bead_claim_checks
          script: sase_job_bead_claim_checks
          trigger:
            provider: fs
            max_quiet: "120s"
            paths:
              - path: projects
                glob: "*/artifacts/.ace_refresh_pulse"
          description: |-
            Acquire missing bead claims for live pre-launch agents and release claims held by dead ones

            Scans pre-launch agent artifacts, backfills a missing claim for a live waiting agent, and releases a
            claimed bead when its unpromoted owner has died. Reconciled dead records are tombstoned so later ticks avoid
            reopening their stores, while a failed project read is retried safely.
        - name: epic_launch_flush
          script: sase_job_epic_launch_flush
          run_every: "30s"
          description: |-
            Flush planner completion notifications orphaned by an unsettled epic launch

            Preserves deferrals while a matching detached epic-launch task is active, flushes unowned deferrals after
            a 90-second grace period with a resume command, and reaps unclaimed settle markers after one hour.
    agent_waits:
      description: |-
        Resolve agent wait dependencies within seconds of completion

        Runs every two seconds so waiting agents resume promptly once a dependency finishes. Only agent
        dependency resolution belongs here; bead-claim work lives in waits and sidecar syncing in sidecar_sync.
      interval: 2
      job_timeout: "2m"
      jobs:
        - name: wait_checks
          script: sase_job_wait_checks
          trigger:
            provider: fs
            max_quiet: "120s"
            paths:
              - path: projects
                glob: "*/artifacts/.ace_refresh_pulse"
          description: |-
            Resolve agent wait dependencies and write ready.json when satisfied

            Walks waiting markers across projects, resolves only live waiters (provably dead runners are skipped and
            counted in the dead-waiter backlog counter), and resolves named-agent, artifact, and closed-bead
            dependencies from filesystem agent metadata and canonical bead state. It writes ready.json only after
            every dependency is satisfied; invalid or already-ready markers are skipped without blocking other agents.
    sidecar_sync:
      description: |-
        Fetch and fast-forward opted-in primary sidecar clones

        Runs every thirty seconds so sidecar clones converge without delaying agent wait resolution. Only the
        sidecar_auto_sync job belongs here; agent dependency resolution lives in agent_waits.
      interval: 30
      jobs:
        - name: sidecar_auto_sync
          script: sase_job_sidecar_auto_sync
          timeout: "2m"
          description: |-
            Fetch and fast-forward opted-in primary sidecar clones (plans, beads, research, custom)

            Scans every enabled project's auto_sync sidecar roles, syncing a role immediately when a publisher left a
            pending hint and otherwise backstopping it at most every five minutes. Projects with a live agent waiting
            on bead completion also hint the beads role every tick, even when that role has not opted into auto_sync,
            so waiters unblock promptly instead of relying on the runner's coarser fallback. Only a clean, attached,
            non-diverged clone with a matching remote is fetched and fast-forwarded; dirty, detached, diverged,
            mismatched, missing, or busy clones are left untouched and reported. Bounded work budget and persistent
            per-role backoff keep one unhealthy clone from stalling the rest. After a beads-role refresh, touches
            the project's completion pulse so agent_waits re-evaluates bead waits on its next 2 s tick.
    checks:
      description: |-
        Poll slower PR-submission and workspace-claim checks on a five-minute cadence

        Runs every five minutes for checks that can tolerate delay or may touch remote PR state, reducing needless
        polling while retaining a cleanup backstop. Fast hook progression, minute-level comments, and hourly
        maintenance deliberately live elsewhere.
      interval: 300
      jobs:
        - name: bead_task_triage
          script: sase_job_bead_task_triage
          timeout: "2m"
          description: |-
            Raise one human gate for each ready or snoozed task bead, and for each due flag-typed task bead

            Scans enabled projects every five minutes and gives every live task bead exactly one pending
            gate: a TaskTriage gate while a task bead is ready and has at least its effective +1 bar in reports
            (its own task type's triage.min_plus_ones, else the global bead.task_triage.min_plus_ones), a BeadSnooze
            wake gate while it is snoozed, and a FlagTriage gate once a flag-typed task bead's date and release removal
            thresholds have both passed. A ready task bead below the +1 bar is withheld from
            triage without changing its stored status, and a gate already raised for a bead that falls below the
            bar is canceled and its notification dismissed. Deterministic gate generations in lane state prevent
            duplicate notifications, a gate of the wrong kind is replaced when its bead's status or due-ness
            changes, and a snoozed bead's notification is re-snoozed to its wake time if it ever drifts. Gates are
            canceled when their beads leave those states, while answered or missing gates can be regenerated
            safely if work remains. Gates stranded by removed projects or forgotten lane state are swept without
            touching projects that are only temporarily unreadable. A gateable bead is deferred while a detached
            launch is still in flight or a live agent is working the bead, and a pending gate is canceled when a
            live agent owns the bead.
        - name: plugins_required
          script: sase_job_plugins_required
          timeout: "2m"
          description: |-
            Raise one human gate per project whose required plugins are missing

            Scans enabled projects every five minutes and compares each project's plugins.required list against
            installed distributions. A project with a missing or version-mismatched required set gets exactly one
            pending PluginsRequired gate offering Install and Dismiss. Install performs one combined install for
            all missing names from the answering surface, sharing a bounded public-index probe and resolving each
            plugin from the index unless public PyPI returns a definitive 404, in which case that plugin uses git.
            The gate stays pending when planning or the uv mutation fails, including when sase is not a uv tool
            install. Dismiss records the decision so the same missing set is not re-offered until it changes. The
            job cancels the gate when the set becomes satisfied. Deterministic generations in lane state prevent
            duplicate notifications. Agent and non-interactive contexts still fail closed and never auto-install.
        - name: pr_submitted_checks
          script: sase_job_pr_submitted_checks
          description: |-
            Start background is_cl_submitted checks for leaf PRs with a submitted parent

            Applies the axe query, finds eligible leaf Patches with PR URLs, and launches non-blocking submission
            checks whose results are collected by pending_checks_poll. A five-minute sync cache suppresses duplicate
            remote work, except that the first cycle checks eligible leaves immediately.
        - name: stale_running_cleanup
          script: sase_job_stale_running_cleanup
          description: |-
            Backstop release of workspace claims and proc rows held by dead processes

            Runs the same all-project dead-process reconciliation as the hooks-lane cleanup, including conservative
            handling of pinned claims with agent artifacts and terminalization of orphaned active proc rows. This
            five-minute placement still frees stale workspace claims and proc rows if the fast hooks lane is disabled,
            restarting, or repeatedly failing.
        # orphan_agent_scope_reap keeps the default `always` trigger: process death
        # leaves no filesystem event to watch, so no fs proxy exists - the same
        # rationale as stale_running_cleanup.
        - name: orphan_agent_scope_reap
          script: sase_job_orphan_agent_scope_reap
          timeout: "2m"
          description: |-
            Reap orphaned agent scopes whose runner died without cleaning up

            Scans sase-agent scopes under the user manager every five minutes and terminates leaked processes in
            scopes with no live runner (SIGKILL, OOM, or crash), using the same spare-process selection rule as the
            runner-exit sweep. Scopes holding a live runner or systemd-run window, scopes younger than
            agent_scope_teardown.reaper_min_scope_age_seconds, and scopes whose every member is a spared shared
            daemon (ssh-agent, gpg-agent, tmux server, ssh ControlMaster) are left untouched. Disabled entirely when
            agent_scope_teardown.enabled is false.
    comments:
      description: |-
        Start background critique-comment checks for mailed PRs every minute

        Runs every minute so reviewer feedback reaches active Patches promptly without polling on every hooks tick.
        Only remote comment-check launches belong here; pending result collection and zombie marking remain in the
        faster hooks lane.
      interval: 60
      jobs:
        - name: comment_checks
          script: sase_job_comment_checks
          description: |-
            Start background critique_comments checks for all mailed PRs

            Applies the axe query and starts non-blocking critique_comments checks for mailed Patches that have an
            available workspace, then records a comment-cycle summary. The routine's one-minute interval is the
            polling throttle; pending_checks_poll later consumes each background result.
    housekeeping:
      description: |-
        Run hourly error digests, notification compaction, artifact previews, and cleanup

        Runs once an hour because notification batching, live-inbox bounding, bounded scratch reclamation,
        artifact/run-retention previews, and stale-backlog cleanup are useful but not latency-sensitive. Put
        durable maintenance that may scan substantial local state here, not lifecycle, dependency, or remote polling
        work.
      interval: 3600
      jobs:
        - name: error_digest
          script: sase_job_error_digest
          description: |-
            Send a notification digest of errors from the last hour

            Reads the AXE error log and the last successful digest timestamp, then notifies only about newer errors
            within the rolling one-hour window. The checkpoint advances to the newest notified timestamp, preventing
            duplicate digests while leaving unsent errors eligible after a notification failure.
        - name: managed_tmp_reap
          script: sase_job_managed_tmp_reap
          description: |-
            Prune stale scratch under the managed SASE temp root

            Removes old children from managed-temp buckets using workload-specific age limits, without following
            symlinks or deleting the stable bucket directories. Launched agents default TMPDIR/TMP/TEMP,
            CARGO_TARGET_DIR, and CARGO_BUILD_BUILD_DIR into managed buckets, and runners remove their own
            launch-assigned scratch at exit when no live process still uses it. The reaper can also prune aged large
            build output early when the managed root exceeds its size target or the filesystem falls below the
            free-space floor; pressure pruning waits for the configured minimum age (12h by default), or 1h once the
            free-space floor is breached, while preserving generic agent scratch, handoff data, unknown buckets, and
            build trees with fresh descendants. Each pass removes at most 2,000 entries and de-indexes deleted
            agent-artifact directories, so a neglected root converges without blocking interactive commands.
        - name: proc_runtime_sweep
          script: sase_job_proc_runtime_sweep
          description: |-
            Prune stale rowless proc runtime directories

            Removes only canonical proc runtime directories that no longer have a durable proc row, are older than
            the configured proc runtime orphan horizon, and are direct non-symlink children of ~/.sase/procs/runtime.
            The Rust owner rechecks the proc store under its lock before deletion so concurrent reservations are
            preserved. Runtime directories for proc rows actually pruned by retention are deleted immediately by the
            proc store path; this job handles historical orphans over a bounded per-pass budget.
        - name: disk_pressure
          script: sase_job_disk_pressure
          timeout: "5m"
          description: |-
            React when SASE's filesystem crosses disk-pressure thresholds

            Checks proportional free-space thresholds, logs and notifies the largest SASE disk owners, then runs
            unattended owner-safe cleanup passes early. Managed temp and proc runtime sweeps may apply because their
            owners encode deletion policy. Artifact run directories, backups, and unowned Cargo-shaped strays are
            reported for human action and are never deleted by this job.
        - name: bead_stale_cleanup
          script: sase_job_bead_stale_cleanup
          timeout: "2m"
          description: |-
            Sweep stale sub-threshold ready task beads into one BeadStaleCleanup gate

            Reads every enabled project's ready task beads and offers those that have sat below
            their effective +1 bar (their own task type's triage.min_plus_ones, else the global
            bead.task_triage.min_plus_ones) for bead.task_triage.stale_after_days once at least
            bead.task_triage.stale_cleanup_min_beads such beads exist. One pending gate at a time
            carries at most 50 beads, oldest first; a larger backlog is reported in omitted_count and
            offered on later ticks. An unchanged roster leaves the pending gate alone. The gate is
            canceled when the backlog drops below the bar.
        - name: artifact_run_prune
          script: sase_job_artifact_run_prune
          timeout: "5m"
          description: |-
            Preview old ace-run directories and empty shard cleanup

            Plans whole-run ace-run retention without deleting anything. The preview keeps the newest
            artifacts.retention.keep_recent_run_months calendar months whole, protects runs referenced by
            artifact files, text refs, agent names, and non-closed beads, and reports empty month/day shards
            outside sase's TUI startup watch window. When unchanged candidates or protection problems remain, it
            upserts one deduplicated Axe report notification that names the preview command. Artifact-run
            deletion is currently preview-only.

Top-level fields:

Field Type Default Description
max_hook_runners int 3 Maximum concurrent hook runners (non-$ hooks) across all Patches.
max_agent_runners int 3 Maximum concurrent agent runners (agents and mentors) across all Patches.
zombie_timeout_seconds int 7200 Seconds after which a running hook or workflow is flagged as a zombie.
query string "" Query string for filtering Patches (empty = all).
job_script_dirs list[string] [] Additional directories to search for external job scripts.
routine_log_max_bytes int 52428800 Maximum bytes retained for each bounded routine log.
routine_log_temp_max_age_seconds int 300 Minimum age before orphaned log-rotation temp files are removed.
routine_restart_backoff_max_seconds int 60 Maximum delay between retries for a crashing routine.
verbose_routine_diagnostics bool false Include verbose diagnostics in job script context JSON.
routines dict - Mapping of routine name -> config (see below).

Routine fields (per entry under routines):

Field Type Required Default Description
description string yes - Summary line, blank line, optional body describing the lane's cadence and work.
interval int no 1 Seconds between job polling cycles.
job_timeout string no - Positive compound duration limit, such as "90s", "1h30m", or "1d".
wait_runners int no - Per-launch capacity budget for proposed lane agents, emitted as %queue(capacity=N); must be at least 1.
env dict[string, env-value] no {} Environment inherited by every job in this routine.
jobs list[object] or map no [] Composable job definitions (see below).

Job fields (per entry under jobs):

Field Type Required Default Description
name string list only - Stable identity; map form uses the entry key.
script string no name Exact executable name; no prefix is added automatically.
enabled boolean no true Soft-disable a keyed entry while retaining inherited fields.
description string yes - Summary line, blank line, optional body describing what the job does.
run_every string no - Positive compound cadence such as "60m", "1h30m", or "1d".
timeout string no - Per-job duration limit. Overrides routine job_timeout.
env dict[string, env-value] no {} Literal values or {env:}, {file:}, {pass:} references.
inhibit_if list or map no - patch / agent_hood / agent_clan / agent_runners guards before dispatch; changespec is a legacy alias.
trigger string or map no always always, git.commits_since, or fs scheduled-run trigger.
once_per string or object no - Bounded per-proposal dedupe-key template.
for_each list or source no - Literal targets or the filtered projects source.
vars object no {} Non-secret values copied to the job context.

Both description fields use one grammar: a non-blank summary line of at most 100 characters, then — if anything follows — a blank line, then a free-form body, with the whole string capped at 2000 characters. Violations produce the description_summary_blank, description_summary_too_long, description_body_separator_required, and description_too_long diagnostics. See AXE — Description Grammar for the full contract, the authoring style guide, and the YAML literal-block form.

Routines were formerly called lumberjacks and jobs were called chops. The old spellings remain accepted, schema-valid input aliases but are marked deprecated: lumberjacks for routines, chops for jobs, chop_timeout for job_timeout, chop_script_dirs for job_script_dirs, and lumberjack_log_max_bytes, lumberjack_log_temp_max_age_seconds, lumberjack_restart_backoff_max_seconds, and verbose_lumberjack_diagnostics for their routine_* counterparts. New configuration should use the canonical keys. With the default-on axe_routine_job_contract sunset flag, sase config show prints the effective AXE block with the canonical names. Legacy sase_chop_* executables still exist beside the sase_job_* entrypoints. See AXE compatibility aliases for the matching CLI, environment, and artifact-reference aliases.

All jobs are scripts. Exact-name resolution checks job_script_dirs, then the running interpreter's bin directory, then $PATH. Invalid fields, duplicate identities, non-positive intervals, and invalid durations fail config loading with a dotted config path and source-layer diagnostic. agent: and macro: are rejected with a migration message.

Environment values resolve at dispatch time. Use a literal for non-secret data or {env: NAME}, {file: path}, and {pass: entry} references for secrets. Routine-level env is inherited by every job, then a job's own env overrides matching names.

The built-in wait_checks job writes ready.json only after named %wait dependencies complete successfully. Failed, killed, crashed, still-running, malformed, or missing done.json artifacts do not satisfy the dependency.

Map form is the composable form. Higher-priority config layers patch matching fields by key, and per-field source provenance is shown by the verbose job inventory:

axe:
  routines:
    docs:
      description:
        Refresh project documentation when repositories accumulate meaningful changes
      interval: 60
      env:
        API_TOKEN: { env: DOCS_API_TOKEN }
      jobs:
        refresh_docs:
          description: Refresh documentation after meaningful repository drift
          script: sase_job_refresh_docs
          run_every: "30m"
          trigger:
            git.commits_since:
              project: "{target.name}"
              threshold: 10
              checkpoint: on_action_success
          for_each:
            source: projects
            vcs: [git, gh]
        packaged_but_disabled:
          description: Retain a packaged documentation check without running it
          enabled: false

for_each produces stable identities such as refresh_docs[sase-core]. Each instance has independent scheduling, history, checkpoints, and once-per state. Target data is available in the context JSON under target and through SASE_JOB_TARGET_KEY / SASE_JOB_TARGET_<FIELD>. Literal target rows may include overrides: for per-target job fields such as run_every and trigger thresholds.

inhibit_if accepts keyed patch, agent_hood, agent_clan, and agent_runners providers. The legacy changespec key remains accepted as an alias. The clan provider requires a case-sensitive name_prefix and checks canonical clan metadata for active agents, including waiting members; it never infers clans from dotted names. agent_runners.max defaults to 0 and inhibits while more than that many participating agent lanes are occupied, a participating-lane count distinct from the weighted %queue(capacity=N) admission budget. Weighted capacity is tracked separately by the runner-capacity snapshot and sase's TUI header. A STARTING agent has not yet been admitted and does not count; an agent parked on a question has yielded its capacity and does not count. trigger accepts always, git.commits_since, or fs; the git provider requires project and threshold, and its checkpoint policy is on_observation, on_action_accepted, or on_action_success. The fs provider requires paths (bare path strings or {path, glob} objects, stat'd shallowly — no recursion, no content reads) and a positive max_quiet duration, fires when its computed state token changes or max_quiet elapses since the last fire, always uses on_observation checkpoint semantics, and fails open (fires without advancing its checkpoint) on an unreadable path. See AXE — Triggers, Guards, Dedupe, and Targets for the full contract. Skips are recorded with reasons. Manual runs bypass the trigger but honor guards; with agent_runners, a manual run while participating lanes are occupied skips unless sase axe job run -f/--force is used. once_per can be a key template string or an object with key and bounded capacity; proposal-supplied dedupe_key values take precedence. dedupe_key is durable work identity, not a retry clock: it stays reserved after a successful no-op launch, so jobs whose work can go stale between scans should recheck eligibility with a proposal %if:: predicate (see Structured Results and Launch Proposals) instead of folding a repository revision into the key. When dedupe removes a proposal from a wait_on chain, AXE walks through the skipped dependencies to the nearest earlier proposal that survives filtering. If none survives, AXE removes the wait. Proposal previews expose the resulting wait_on value and explain a relink in dedupe_reason.

The builtin sase_job_refresh_docs emits an update proposal plus a polish proposal that waits for the update. It uses the target source's workspace, while cadence and commit thresholds stay declarative in configuration. Its default prompts are strictly documentation-scoped and tell agents to report suspected code bugs instead of fixing them. The defaults can be replaced with non-blank vars.prompt and vars.polish_prompt strings; operators are responsible for the scoping language in replacement prompts. See Axe structured results and launch proposals for the result document, proposal fields, lifecycle statuses, and debugging commands.

Every job entry must carry a description following the description grammar. Bare-string list entries are no longer valid because they cannot carry one; use map form or object-form list entries:

jobs:
  # Object-form list entry
  - name: hook_checks
    script: sase_job_hook_checks
    description: Check for completed or failed hooks
  - name: custom_job
    script: my_full_executable_name
    description: Run custom analysis
    run_every: "1h30m"
    env:
      MY_API_KEY: { env: MY_API_KEY }

CLI flags on sase scheduler run override max_hook_runners, max_agent_runners, zombie_timeout_seconds, and query for a single run (see CLI Flags).

Source: src/sase/axe/config.py, src/sase/default_config.yml

file_hooks

Defines non-gating commands that run once per matching file event. Use sase file-hook list to inspect the effective hooks, including the config layer that contributed each entry; add -j/--json for machine-readable output. Use sase file-hook history and sase file-hook show to inspect producer audits for whether a matching event was dispatched, unmatched, or failed before a command ran.

file_hooks:
  - name: research-highlights
    description: Render new research reports into Highlights PDFs.
    command: bob highlights create
    filters:
      projects: [sase]
      sidecars: [research]
      path_globs: ["20*/**/*.md", "!20*/*__*.md", "!20*/*/*__*.md"]
      agent_name_globs:
        - "!research.*.cdx"
        - "!research.*.cld"
        - "!research.*.grk"
        - "!research.*.mus"
        - "!research.*.gem"
      ops: [ADD]
      producers: [commit, sdd, finalizer]
    timeout: 120s

Hook fields:

Field Type Required Default Description
use string no - Installed sase_file_hooks provider, qualified <plugin>@<id>, whose template supplies base fields.
name string yes* provider ID Unique lowercase slug shown in notifications and sase file-hook list.
description string no provider Human-readable purpose for the hook.
command string yes* provider Shell command; the matched absolute file path is appended as its final arg.
filters object no {} / provider Event-selection criteria. Omitted or empty means unrestricted.
timeout duration no 120s / provider Per-run integer duration with an ms, s, m, or h suffix.

Without use, name and command are required. With use, SASE deep-merges the local entry over the installed provider's template, defaults name to the provider ID, and requires any fields the provider marks as local. use must be qualified as <plugin>@<id>, where <plugin> is the literal builtin or the installed distribution name; a bare, unprefixed, or unknown use value disables only that invalid entry, is reported while sase file-hook list loads effective hooks, and also fails sase doctor -C config.file_hooks and sase validate so a disappeared hook can never go unnoticed. A plugin can therefore ship safe defaults while requiring the machine-specific command or destination in user configuration.

Filter fields under filters:

Field Type Required Default Description
filters.projects list[string] no all projects User-facing project names.
filters.sidecars list[string] no all repos Sidecar role names such as research, plans, or beads.
filters.path_globs list[string] no all files Repo-relative POSIX globs; ! prefixes a veto exclusion.
filters.agent_name_globs list[string] no all agents SASE agent-name globs matched against the agent that produced the event; ! prefixes a veto exclusion.
filters.ops list[string] no all operations Any subset of ADD, MODIFY, and REMOVE.
filters.causes list[string] no none Non-user event causes to accept in addition to ordinary user commits; currently artifact_links.
filters.producers list[string] no all producers Any subset of artifact, commit, sdd, finalizer, and dispatch.

Matching semantics:

  • Event sources. Hooks receive files from commits created by sase stitch create (producer commit), commits written through the SDD sidecar commit path (producer sdd), and sase artifact create (producer artifact, treated as ADD). Finalizer reconciliation re-derives a commit's events (producer finalizer). Direct engine dispatch uses producer dispatch.
  • Ops. Commit operations come from git diff --name-status; renames split into REMOVE plus ADD, root-commit files are ADD, and unknown status letters fold to MODIFY.
  • Path glob matching. filters.path_globs match the file's repo-root-relative POSIX path. Positive globs are OR-ed, while any matching ! negative veto-excludes the file. * does not cross /; ** does. A negative-only list means “everything except.” Dotfiles are eligible.
  • Agent-name matching. filters.agent_name_globs match the resolved SASE agent name. Positives are OR-ed and ! vetoes, exactly as for paths. Agent names contain no /, so * spans the whole name (research.*.cld matches research.7.cld). An event with no resolvable agent name matches a negative-only list, but never a list containing any positive pattern.
  • Attribution. The agent name is resolved in the producing process from $SASE_ARTIFACTS_DIR/agent_meta.json's name, falling back to $SASE_AGENT_NAME, so a commit made outside a SASE agent has no agent name.
  • Causes. Ordinary user-originated commits always remain eligible. Internally generated commits are ignored unless their cause appears in filters.causes; the current non-user cause is artifact_links, used when SASE updates managed Links and Referenced By blocks in artifact sidecars. This opt-in prevents a projection update from recursively triggering ordinary document-processing hooks.
  • Filters. All configured dimensions are AND-ed. filters.projects compares alias-resolved, user-facing project names, never ProjectSpec keys. filters.sidecars compares sidecar role names. filters.producers compares the dispatch producer identity; omitting it matches every producer. A project-local sase/sase.yml declaration without filters.projects is automatically scoped to the detected project.
  • Execution. Runs are post-write and non-gating. Hook configuration, matching, persistence, spawn, or command failures never fail or block an artifact copy or a commit. Each matched command runs with the absolute path appended as a shell-quoted final argument and reports success or failure through a SASE notification.
  • Artifact store paths. Artifact events match against the original repository-relative path, but the detached command receives the durable stored copy. That copy is content-addressed and may use a digest-suffixed basename such as <stem>-<sha256-prefix>.md. Hooks whose output identity depends on the input basename should set filters.producers to the committed-file producers (commit, sdd, finalizer) so they do not run against the stored copy.
  • Producer audits. Every configured-event dispatch attempt persists a bounded record under the file-hook state root (audit/) before returning. That record explains a filter miss, a dispatched or already-present batch, or a producer failure without relying on Python logging. Successful matches continue to use batches and run logs as the execution record. Audits follow the same 30-day retention as batches, runner logs, and run logs.
  • Producer failures vs command-run failures. A producer failure is a problem before a hook command runs: config load, event capture, batch persistence, or runner spawn. Those create one non-gating file-hooks error notification that points at the audit record. Ordinary no-hooks and filter-miss outcomes stay quiet. A command-run failure is the hook command itself exiting non-zero or timing out; that evidence stays on the batch run log and the existing per-run notification.
  • Finalizer reconciliation. After a successful built-in commit marker is verified, finalization re-derives the committed file events and ensures the deterministic commit batch exists. An already-dispatched batch is reused without spawning again; a missed first dispatch is retried.

The user layer (~/.config/sase/sase.yml) replaces the bundled/default file_hooks list. Selected machine overlays (sase_*.yml) and project-local sase/sase.yml concatenate entries onto the effective list, matching mentor_profiles merge behavior. Hook names must remain unique across the effective list; invalid or duplicate entries are warned about and skipped. Unknown file_hooks keys are rejected the same way, so a hook carrying one is skipped with a warning rather than silently losing that filter. Filter fields at the hook top level are no longer accepted; move projects, sidecars, path_globs, agent_name_globs, ops, causes, and producers under filters. Note that globs was renamed to filters.path_globs; the old key is not accepted.

Source: src/sase/config/file_hooks.py, src/sase/file_hooks/engine.py, src/sase/file_hooks/audit.py, src/sase/file_hooks/producer.py, src/sase/config/sase.schema.json

plugins

Declares the distributions this project needs installed in the running environment. A linked or sidecar checkout is not an install.

plugins:
  required:
    - sase-github
    - sase-research-artifacts>=0.2
Field Type Default Description
plugins.required string[] [] PEP 508 requirement strings checked against installed distributions. Duplicate names are an error.

Every non-builtin <plugin>@ prefix used anywhere in this project config — artifact references, file hooks, and bead.task_types[].use — must name a distribution listed here. That single rule keeps a project's declared dependencies honest.

Enforcement is graded by blast radius:

Surface Behavior
sase memory init, sase validate Hard error, raised before any memory-drift comparison so a missing plugin never looks like drift
sase bead create -T 'task(<slug>)' for a missing plugin's slug Hard error naming the plugin and sase plugin install <name>
sase doctor -C plugins.required ERROR severity, listing each missing requirement and the install command
Interactive human CLI and sase's TUI A PluginsRequired gate offering to install
Agent / non-interactive contexts Fail closed with the human-directed command; never auto-install
sase bead show / list of an unknown type Degraded render, never a failure

use: values themselves must be qualified as <plugin>@<id>, where <plugin> is the literal builtin or a distribution name. A bare value is a hard error that names the correct replacement when the live registry can resolve it.

See Task Types for how required plugins feed the committed sase/task_types.json snapshot, and Required Plugin Notification for the human install offer.

Source: src/sase/plugins/required.py, src/sase/config/sase.schema.json

mentor_profiles

Defines mentor agents that run automated code reviews when a Patch's diff, changed files, or amend notes match configurable criteria. Each profile groups one or more mentors with shared matching rules. See docs/mentors.md for the full mentor system reference.

mentor_profiles:
  - profile_name: python_review
    file_globs:
      - "*.py"
    mentors:
      - mentor_name: style_checker
        role: "Python style expert"
        focus_areas:
          - focus_name: style
            description: "PEP 8 compliance and code style"
          - focus_name: naming
            description: "Variable and function naming conventions"

  - profile_name: first_commit_review
    first_commit: true
    mentors:
      - mentor_name: architecture
        role: "Software architect"
        focus_areas:
          - focus_name: design
            description: "Overall design and architectural patterns"

Profile fields:

Field Type Required Description
profile_name string yes Unique name identifying this profile.
mentors list yes List of mentor definitions (see below).
file_globs list[string] no* Glob patterns matched against changed file paths.
diff_regexes list[string] no* Regex patterns matched against the diff content.
amend_note_regexes list[string] no* Regex patterns matched against commit/amend notes.
first_commit bool no If true, match only on the first commit of a Patch.
projects list[string] no Only match Patches in these projects. Auto-set for local sase.yml profiles.

*At least one of file_globs, diff_regexes, amend_note_regexes, or first_commit must be provided per profile.

Mentor fields:

Field Type Required Description
mentor_name string yes Unique name identifying this mentor within its profile.
role string yes Role or persona for the mentor (e.g., "Security reviewer").
focus_areas list[object] yes List of review focus areas (see below).

Focus area fields:

Field Type Required Description
focus_name string yes Short name for this focus area (e.g., "correctness").
description string yes Description of what this focus area reviews.

Mentors run automatically on Patches with Ready or Mailed status when their matching criteria are met. Mentor comments are structured JSON with severity levels (error, warning, suggestion) that can be reviewed and applied through the Mentor Review modal in sase's TUI (,C).

Source: src/sase/config/mentor.py

metahooks

Metahooks intercept failing hooks before the summarize agent runs. They match based on the hook command (substring match) and the hook output (regex match). When a metahook matches, it can trigger specialized handling instead of the default summarization.

metahooks:
  - name: scuba
    hook_command: sase_hg_presubmit
    output_regex: "SCUBA_ERROR.*timeout"

  - name: flaky_test
    hook_command: blaze test
    output_regex: "FLAKY"
Field Type Required Description
name string yes Unique identifier for this metahook.
hook_command string yes Substring matched against the executed hook command.
output_regex string yes Regex pattern matched against hook output (multiline).

Source: src/sase/config/metahook.py

macros

Defines reusable prompt snippets that can be referenced with #name syntax in any prompt. Supports both simple string content and structured definitions with typed inputs and Jinja2 templates.

macros:
  # Simple string format
  greeting: "Hello, please review this code."

  # Structured format with inputs
  review:
    input:
      language: word
      strict: { type: bool, default: false }
    content: "Review this {{ language }} code.{{ ' Be strict.' if strict }}"

  # With tags for semantic role lookup
  my_crs:
    content: "Summarize the code review..."
    tags: [crs]

Macros use the shared first-wins content-layout order:

  1. Project sase/macros/, then legacy project .macros/ and macros/
  2. Home ~/sase/macros/, then legacy home ~/.macros/ and ~/macros/
  3. ~/sase/macros/{project}/, then legacy ~/.config/sase/macros/{project}/
  4. Project sase/sase.yml (root sase.yml is an exclusive legacy fallback)
  5. User sase_*.yml overlays, then ~/.config/sase/sase.yml
  6. Plugin config and package default config
  7. Plugin macro resources
  8. <sase_package>/default_macros/*.md, then <sase_package>/macros/*.md

Earlier sources win on name conflicts. Project and home canonical directories are the only writable filesystem destinations; legacy directories remain read-compatible but are not offered for new saves. File-based macros use YAML front matter for metadata and the file body for content. The Macro discovery table lists every source separately.

Source: src/sase/macro/loader.py

macro_aliases

Defines raw text-level alias substitutions that are applied before any macro processing. This is useful for creating shorthand references where the alias must be present in the raw text for other processing logic (such as VCS directory-switching) to work correctly.

macro_aliases:
  c: commit # #c → #commit
  p: propose # #p → #propose
  deploy_notes: "release-notes" # #deploy_notes → #release-notes
  gh_foo: "gh:foo/bar" # #gh_foo → #gh:foo/bar
Field Type Default Description
macro_aliases dict[string] {c: commit, p: propose} Mapping of alias name → target. Applied as text substitution

The built-in defaults provide #c as a shorthand for #commit and #p for #propose. Additional aliases can be added in user config files.

Each entry maps an alias name to a target string. When the processor encounters #alias_name in a prompt, it replaces it with #target before any other macro resolution occurs. Only #-prefixed references are substituted; the alias name must match [a-zA-Z_][a-zA-Z0-9_]*.

Source: src/sase/macro/processor.py

use_chezmoi

Enables chezmoi-aware home-file writes. When set to true, SASE writes generated home instructions, memory, skills, and home-directory macro paths through the chezmoi source tree under ~/.local/share/chezmoi/home/ instead of writing the live home files directly. Canonical ~/sase/macros/ and ~/sase/memory/ map to source paths home/sase/macros/ and home/sase/memory/. The unchanged global config still maps to home/dot_config/sase/sase.yml.

This affects initialization workflow as well as macro editing. sase memory init targets the chezmoi home source root when it needs to initialize home-level AGENTS.md, writes home memory there, and may run the configured chezmoi deploy path; sase skill init writes provider skill files there before optional commit, push, and apply steps.

Home-level provider instruction files (CLAUDE.md, GEMINI.md, QWEN.md, OPENCODE.md) in the chezmoi source are byte-for-byte copies of the root's generated AGENTS.md. When no machine overlay declares memory.h1_title, those copies stay static .md files. When a machine overlay does declare a title, sase memory init writes AGENTS.md.tmpl and preferred *.md.tmpl shims whose H1 switches on .chezmoi.hostname, and migrates leftover static copies away. Legacy *.md.tmpl shims that imported @{{ .chezmoi.homeDir }}/AGENTS.md are still recognized and migrated to full copies.

use_chezmoi: true # default: false
Field Type Default Description
use_chezmoi bool false Write home-managed SASE files through the chezmoi source directory.

Source: src/sase/config/core.py

commit_hooks

Shell commands that bracket commit-producing VCS dispatches. before runs in the repository root after bead and plan mutations but before diff capture and dispatch. after runs in the repository root only after create_commit or create_pull_request succeeds, including its push where applicable. Proposals run before but never run after because they save a diff without creating a commit.

Both fields default to an empty string. Because the object is deep-merged, a global before hook and project-local after hook compose without either configuration repeating the other phase.

commit_hooks:
  before: "just fix" # default: ""
  after: "chezmoi update -a --force" # default: ""
Field Type Default Description
commit_hooks.before string "" Command before diff capture and VCS dispatch. Empty means disabled.
commit_hooks.after string "" Command after a commit/PR dispatch and push succeed. Empty means disabled.

Hook output is captured and a bounded stdout/stderr tail is printed on failure; each run also leaves per-run evidence files described in Commit Hook Evidence. A failing before hook aborts before dispatch. A failing after hook leaves the commit checkpoint in place and returns failure even though the commit may already be pushed; fix the command and run sase stitch create --resume. The completed after-hook step is checkpointed so a normal resume does not rerun it. A crash after the external command succeeds but before that checkpoint write can run it again, so after commands must be safe to repeat.

Source: src/sase/default_config.yml, src/sase/workflows/commit/commit_hooks.py, src/sase/workflows/commit/workflow.py

gate

Settings for durable, command-backed gates. The only current setting controls how the hourly gate_turn_reclaim housekeeping job settles a gate turn whose gate has already passed its own deadline.

gate:
  turn:
    reclaim_grace_seconds: 3600
Field Type Default Minimum Description
gate.turn.reclaim_grace_seconds int 3600 0 Seconds after a turn gate's deadline before reclaim force-settles the pending turn as lost.

Within the grace window, reclaim cancels an expired gate and settles its turn as a normal timeout; once the window has passed, a still-pending turn settles as lost instead. A missing, negative, or non-integer value falls back to 3600. While legacy_sase_shell_syntax is enabled, the retired gate.shell .reclaim_grace_seconds key is accepted as this same setting. Setting both keys is an error; use only gate.turn.

Source: src/sase/default_config.yml, src/sase/gate_turn/reclaim.py

max_running_agents

The configured global runner-capacity budget across all projects. The key remains an integer and the packaged default remains 10, but the value is interpreted as capacity units. A normal launch claims 1.0; %queue(weight=...) / %q(w=...) can request a non-negative finite weight (%q(w=0) adds no load but still takes its queue turn).

A standalone agent owns one claim of its effective weight. A live serial session shares one claim across its agent, monitor, and serial successor turns; serial continuations inherit the session weight when omitted, and must reacquire capacity after the session has released its claim. Independently launched clan members and live parallel clan members each hold their own claim. A processless gate turn deliberately releases runner capacity while it owns a user decision, even when it retains the session's workspace claim. Workflow Python/bash steps and axe Patch runners hold none of this capacity; axe runners continue to use their separate axe.max_*_runners limits.

max_running_agents: 10
Field Type Default Minimum Description
max_running_agents int 10 1 Configured runner-capacity units available on this host.

The effective cap is an active machine-wide temporary override first and this merged configured value second. In Launch Control, fixed Ctrl+R opens Runner Capacity: e previews and writes the user-base/chezmoi source, o chooses a relative, custom, until-cleared, or exact-time override, and x clears it. Temporary state is stored as a versioned record at ~/.sase/max_running_agents_override.json; a new set replaces the previous value, expiry is enforced at its deadline, and a persistent edit leaves an active override in force. Lowering the effective value is non-preemptive, so existing agents continue and new launches wait for occupied capacity to drain. Parked waiters and question continuations reread the effective cap on each normal poll. With the default-on queue_capacity_budget sunset flag, an explicit %queue(capacity=N) (or sase bead work -c/--capacity N for an epic) uses that positive-integer value as the launch's own admission budget, replacing the global budget for that launch only. Once admitted, the launch holds an ordinary weighted claim, so occupied capacity can honestly exceed the global budget until work drains.

An authored %queue(capacity=<M>x) instead scales from this effective value, including an active override: %q:1.5x resolves to 7.5 when the effective limit is 5. It is resolved again while the launch is queued, rather than freezing the configured value at launch time. Multipliers accept up to two decimal places and affect admission only while queue_capacity_budget is enabled.

When upgrading from an unweighted scheduler build, restart sase's TUI and the service host and let already running agent processes finish or relaunch them under the new binary. Legacy records without queue_weight still read as 1.0, but mixed old and new admission processes do not provide a safe weighted-capacity rollout because old binaries do not enforce weighted claims.

max_agent_pipe_chain

The bound on how many times one agent session may hand its turn forward with sase pipe. The originally launched agent is depth 0; each successful pipe records pipe_depth on the successor and increments it by one. A pipe is refused when the next link would exceed this value, and the refusal names the limit, this configuration key, and the chain length already reached. The calling agent stays alive on a refusal, so it can finish the work itself instead of handing it on.

max_agent_pipe_chain: 8
Field Type Default Minimum Description
max_agent_pipe_chain int 8 1 Maximum sase pipe hops in one agent session chain.

This is a configuration field rather than a feature flag: the number is one users choose permanently to stop a self-piping chain from running away. A missing or malformed value falls back to the packaged default rather than allowing an unbounded chain. Only sase pipe records pipe_depth, so a plan-approval, question, or monitor follow-up member created in between starts the count over at 0. The bound is per session chain, not per host, so it is unrelated to the concurrency cap in max_running_agents.

Source: src/sase/default_config.yml, src/sase/config/core.py, src/sase/main/pipe_handler.py

runner_slots

Bounded deference for deprioritized runner-slot waiters. Admission sorts eligible waiters by lower numeric %queue(priority=N) first, but that sort only compares agents already parked at the instant a slot frees. Dependency-chained work joins the queue seconds after its predecessor exits, so a long-parked deprioritized waiter would otherwise reliably win that race against exactly the normal-priority work it was meant to yield to. A waiter whose priority is numerically worse than the 10 default therefore holds back for a bounded window instead of claiming the moment it becomes eligible, which gives a better-priority agent time to park and win through the existing sort.

runner_slots:
  deference_seconds_per_step: 3
  deference_max_seconds: 60
Field Type Default Minimum Description
runner_slots.deference_seconds_per_step int 3 0 Seconds of deference added per priority step worse than the default.
runner_slots.deference_max_seconds int 60 0 Upper bound on the deference window regardless of priority.

The window is min((priority - 10) * deference_seconds_per_step, deference_max_seconds) seconds. With the defaults, priority=20 defers for up to 30s and any priority at or beyond 30 clamps to the 60s cap. Priority 10 is the boundary and it is inclusive: default-priority and better-priority waiters (priority <= 10) never defer and claim on the first eligible poll exactly as before. The asymmetry is deliberate—you cannot defer to work that may never arrive, so only an agent that explicitly volunteered to be deprioritized pays a delay.

Deference measures continuous eligibility, not total time parked. The window starts when the waiter first becomes eligible while a better-priority agent is pending, and it resets whenever the waiter stops being eligible, so time spent parked behind a full cap never counts toward it. It also exits early: on any poll where no live, unstarted, not-yet-parked agent with a better priority remains, the waiter claims immediately instead of serving out the rest of the window. The agent log prints one Deferring for up to Ns (priority N) line on entry into the window.

Both fields are read fail-open. Deference is a politeness optimization, so a missing, non-integer, negative, or unreadable value falls back to the built-in default rather than propagating an error. This differs deliberately from max_running_agents, where configuration errors do propagate: a bad value here must never strand a runner.

Bounded deference is not priority aging and not preemption. A running agent is never stopped to make room, and a deferred waiter's own priority does not improve while it waits. See Agent waiting for a runner slot for diagnosis, and %queue(priority=N) for the directive itself.

agent_scope_teardown

Lifetime of processes an agent starts. An agent runner's sase-agent-* scope bounds every process the agent starts: leftovers are swept at runner exit and between in-process successor turns, and long-lived work must escape via detach_scope into its own scope. Members matching a spare pattern (plus their descendants) are never killed, so shared user daemons survive.

agent_scope_teardown:
  enabled: true
  term_grace_seconds: 3
  reaper_min_scope_age_seconds: 120
  spare_process_patterns:
    - "^ssh-agent$"
    - "^gpg-agent$"
    - "^tmux: server$"
    - '^ssh: .*\[mux\]$'
Field Type Default Description
agent_scope_teardown.enabled bool true Sweep the runner's own scope at exit and between successor turns.
agent_scope_teardown.term_grace_seconds number 3 Grace between SIGTERM and SIGKILL when sweeping leaked processes.
agent_scope_teardown.reaper_min_scope_age_seconds number 120 Minimum scope age before the orphaned-scope reaper may sweep it.
agent_scope_teardown.spare_process_patterns string[] ssh-agent, gpg-agent, tmux server, ssh mux (see above) Regex list (re.search over comm or cmdline) never killed by a sweep.

This is a config field, not a feature flag: users may permanently disable it or extend the spare list, and the sweep is on by default. An invalid spare pattern is logged and ignored. The sweep is Linux/cgroup-v2 only and a no-op elsewhere.

Source: src/sase/default_config.yml, src/sase/config/_settings_system.py, src/sase/agent/scope_sweep.py

agent hold limits

Three top-level keys bound agent holds armed by the sase agent hold create / run commands and the %hold directive.

agent_hold_default_ttl: 2h
agent_hold_max_ttl: 12h
agent_hold_confirm_capture_threshold: 10
Field Type Default Description
agent_hold_default_ttl string 2h TTL for a hold armed without an explicit TTL. Bare seconds or a number suffixed with s, m, or h.
agent_hold_max_ttl string 12h Largest TTL a hold may request. sase agent hold create / run reject a larger -T/--ttl.
agent_hold_confirm_capture_threshold int 10 Frozen pending capture size above which a launch surface may ask for confirmation of the hold preview.

The launch preview lists each %hold with its scope and a TTL resolved against these values, showing both the default and the cap. sase's TUI and an interactive sase run ask for confirmation when a preview combines future with scope=host, regardless of the threshold. They can also ask when a pending capture exceeds the threshold, but the capture check needs project context: the TUI supplies it, typed launch plans can resolve it, and a plain project-scoped sase run prompt currently cannot. The Admin Center Config tab's Holds child lists and releases active holds, and sase doctor -C agent_holds.stale reports holds whose armer died or whose TTL passed.

A missing or unparsable TTL, or a negative or non-integer threshold, falls back to the default shown above rather than failing the command.

Source: src/sase/default_config.yml, src/sase/config/_settings_runner.py, src/sase/agents/cli_hold.py, src/sase/agent/launch_hold_preview.py

procs

Durable proc records live in ~/.sase/procs/procs.jsonl, with combined output logs under ~/.sase/procs/logs/. Retention keeps every pending or running proc plus the newest configured number of finished procs. Lowering the limit trims the oldest finished rows and their logs; active work is never pruned. Finished procs tagged command-line (TUI Command Line submissions) keep a separate bucket of 50 beside this limit, so heavy Command Line use never evicts operational proc history.

Each proc also has a runtime directory under ~/.sase/procs/runtime/. Retention deletes a pruned row's runtime directory immediately, while the hourly proc_runtime_sweep housekeeping job removes older rowless (orphaned) runtime directories within the horizon and per-pass budget below. Fresh orphans are left alone so a launch that is still reserving its row is never raced.

procs:
  history_limit: 100
  runtime_orphan_horizon_seconds: 259200
  runtime_orphan_max_removals: 2000
Field Type Default Minimum Description
procs.history_limit int 100 1 Number of finished procs to preserve.
procs.runtime_orphan_horizon_seconds int 259200 0 Age before the hourly proc runtime owner may remove rowless runtime directories.
procs.runtime_orphan_max_removals int 2000 1 Maximum historical rowless proc runtime directories removed in one pass.
tasks.history_limit int 100 1 Deprecated alias for procs.history_limit; use procs.* for proc runtime retention settings.

The top-level tasks block is still schema-valid, and sase config layers and sase doctor flag it for migration. In practice the legacy key has no effect in a normal merged configuration: the bundled defaults always supply procs.history_limit, which takes precedence over tasks.history_limit. Move the value to procs.history_limit to change retention.

service

The per-machine service host supervises configured daemon procs and transient oneshots. Its catalog is deliberately machine-owned: builtin defaults, plugin defaults, user config, and machine overlays compose field by field, while project-local sase/sase.yml service: entries are ignored — the host resolves machine-level layers only, never a project-local sase.yml. Lists replace earlier lists whole; env is the one map that merges key by key. A section-level error fails closed, while an invalid individual entry stays visible as unavailable and does not block valid peers.

service:
  procs:
    scheduler:
      builtin: scheduler
      description: "SASE's background automation: routines and their jobs."
    gateway:
      builtin: gateway
      description: "Mobile gateway HTTP API for remote agent dispatch."
      enabled: false
    indexer:
      command: [my-indexer, --watch]
      cwd: ~/work
      env:
        LOG_LEVEL: info
      restart: on-failure
      after: [scheduler]

The shipped scheduler entry is enabled and owns routine/job automation. The shipped gateway entry is disabled so a default host does not expose a mobile API on machines that never paired a phone; it is enabled per machine in the overlay where the gateway is wanted. Builtin launchers must use the entry's own reserved name. A custom entry instead supplies exactly one command: a string runs through sh -c, while a string array runs directly as argv.

Field Type Default Description
service.procs.<name>.description string - Summary line, then a blank line and an optional body (grammar); shown in the Services-tab description panel and sase service proc show. A missing blank separator is tolerated: the body starts at line 2.
service.procs.<name>.enabled bool true Desired machine state. Plugin entries and the builtin gateway default to false; a saved machine override can replace it.
service.procs.<name>.mode daemon daemon Config entries are daemons. Oneshots are transient submissions from sase service proc run.
service.procs.<name>.command string or non-empty string array - Shell command or exact argv. Mutually exclusive with builtin.
service.procs.<name>.builtin scheduler or gateway - Reserved packaged launcher. Mutually exclusive with command and must equal the entry name.
service.procs.<name>.cwd string inherited Working directory for the child.
service.procs.<name>.env string map {} Extra environment. ${NAME} expands a captured variable at launch; $$ is a literal dollar sign.
service.procs.<name>.restart always, on-failure, or never on-failure Restart policy.
service.procs.<name>.success_exit_codes integer array [] Additional 0-255 exit codes treated as clean; zero is always clean.
service.procs.<name>.stop_signal common signal name SIGTERM Graceful stop signal, with or without the SIG prefix.
service.procs.<name>.stop_timeout seconds, > 0 and <= 3600 10 Grace period before SIGKILL.
service.procs.<name>.after name array [] Start ordering only. Unknown names warn and are dropped; a cycle makes every entry in it unavailable.
service.procs.<name>.log_max_bytes integer, at least 4096 2097152 Maximum retained bytes in the rotated proc output log.

A bare command array's first element (for example sase_job_tg_inbound) is looked up on PATH first and then in the bin directory of the interpreter running the host. The second lookup is what makes a plugin's console scripts work under uv tool install --with, which links only the primary package's entry points onto PATH. A PATH hit always wins, an element that contains a path separator is used as written, and the remaining arguments are never changed. The string (shell) form gets no such fallback, because the shell resolves the name; prefer the array form for plugin scripts. sase service init warns when an enabled entry's executable resolves nowhere.

sase service proc enable/disable writes machine-local effective enablement without editing YAML. stop changes only the current boot: it records a marker that is cleared on the next host boot. start and restart record a durable, numbered request that also clears that marker; the host consumes it and the CLI waits for the confirmed pid. Use sase service proc show NAME to see where an effective entry and enablement came from, plus the current restart decision and any pending request.

Restart policy: always restarts after every exit and on-failure after a non-clean one, each with a backoff that starts at 1 second and doubles up to 60 seconds. A clean exit is exit code 0, a success_exit_codes code, or death by SIGTERM, SIGINT, SIGHUP, or SIGPIPE; a stop the host itself requested is never restarted. Three restart-triggering exits or spawn failures within 60 seconds mark the proc crash_loop and raise one service notification per crash-loop episode; a run of at least 5 minutes resets the backoff and ends the episode. When the policy decides not to restart — a clean exit under on-failure, or any exit or spawn failure under never — the host parks the proc instead of relaunching it on the next reconcile, and raises a service notification when the proc is still desired running. A parked proc stays down until an explicit start/restart request, a changed entry, a disable-then-enable cycle, or a restart of the service host itself. If the service.procs configuration stops loading, the host keeps supervising its last-known-good configuration and surfaces the load error as the host error in sase service status; a host that has never loaded a valid configuration launches nothing until it does.

sase service init --yes installs an idempotent user unit (sase.service under systemd --user on Linux, sh.sase.service as a macOS LaunchAgent), captures only the allow-listed provider credentials, PATH, SASE_TMPDIR, SASE_HOME, and configured mobile credential environment variable into a mode-0600 file, retires legacy gateway/AXE units, enables the unit, and starts it. SASE_FEATURE_FLAGS is never captured: the host resolves flags from saved state. Planning and diffs redact every captured value. Linux warns when user linger is off because the service may then stop at logout. See sase service for lifecycle commands.

Source: src/sase/default_config.yml, src/sase/config/sase.schema.json, src/sase/service/config.py, src/sase/service/platform.py

disk

Disk-pressure thresholds combine absolute byte floors with proportional free-space floors, so large volumes warn before they are almost out of bytes. The doctor resource check uses these values, and the hourly disk_pressure housekeeping job uses the warn threshold to decide when to notify and run owner-safe cleanup early.

disk:
  pressure:
    warn_free_percent: 5.0
    error_free_percent: 1.0
    top_owner_min_bytes: 1073741824
Field Type Default Range Description
disk.pressure.warn_free_percent number 5.0 0-100 Warn when filesystem free space falls below this percentage.
disk.pressure.error_free_percent number 1.0 0-100 Error when filesystem free space falls below this percentage.
disk.pressure.top_owner_min_bytes int 1073741824 >= 0 Minimum unowned row size named directly in pressure diagnostics.

sase doctor still honors its absolute floors of 3 GiB for WARN and 1 GiB for ERROR; the effective threshold is the larger of the absolute and proportional values. Use sase disk list to see SASE disk usage by owner and sase disk reap to preview or run the owners' cleanup passes.

Source: src/sase/default_config.yml, src/sase/core/disk_pressure.py

managed_tmp

Age horizons and pressure thresholds for the managed SASE temp root: $SASE_TMPDIR when set, otherwise $SASE_HOME/tmp (~/.sase/tmp by default). SASE writes scratch there in per-purpose bucket directories, and launched agents get TMPDIR/TMP/TEMP, CARGO_TARGET_DIR, and CARGO_BUILD_BUILD_DIR pointed into managed buckets. Runners remove their own launch-assigned scratch at exit when no live process still uses it; sase disk reap and the hourly managed_tmp_reap housekeeping job clean up the rest with these values.

Each bucket belongs to one horizon category. Editor, wrapper, per-agent agent-tmp, and similar command scratch uses the command-scratch horizon; handoff, gh-diffs, and muse-prompts use the handoff horizon; build-targets and cargo-targets use the build-scratch horizon; and launch-prompts, screenshots, and workflow-artifacts, which sase's TUI and screenshot tooling read back after a run ends, use the run-artifact horizon. Unknown buckets and stray top-level entries are aged at the handoff horizon. The bucket mapping is fixed; only the durations and pressure thresholds are configurable.

Between the age horizons runs a dead-launch backstop over the launch-keyed buckets (agent-tmp, cargo-targets, legacy build-targets) for launches that never ran runner-exit cleanup — crashed, SIGKILLed, OOM-killed, or handed off to a monitor/gate follow-up, which mints a fresh scratch key and leaves the old one quiet. Entries whose newest descendant write predates dead_launch.grace_seconds and that no live process holds (by environment or working directory, observed through procfs in one batch scan) are removed largest-first within the removal budget. Held and incomplete-observation entries are preserved and counted; hosts without readable procfs skip the pass and keep the age horizons as the fallback.

Pressure pruning is a later pass that removes aged, large build scratch early when the managed root grows past pressure.max_bytes or the filesystem's free space drops below pressure.min_available_bytes. It never prunes generic agent scratch, handoff data, unknown buckets, or build trees with fresh descendants. While the procfs observer is available it is liveness-aware: a held entry is never removed, an incomplete observation is preserved, and an unheld entry needs only the dead-launch grace rather than pressure.min_age_seconds.

Every root get_sase_managed_tmpdir() writes into is recorded in a Rust-owned registry at $SASE_HOME/managed_tmp/roots.json (schema version plus {path, first_seen_epoch, last_seen_epoch} entries, written atomically under a lock file). The hourly managed_tmp_reap job, the disk_pressure cleanup, and sase disk reap each reap the effective root plus every registered root that still exists, so scratch is bounded no matter which environment its writer resolved. A root nested inside another covered root is folded into it, so it is not reaped or counted twice. Registration is fail-open and never breaks a launch; only absolute, non-symlink directories enroll, and broad roots such as /tmp or $HOME are refused.

In practice, the housekeeping managed_tmp_reap job, the disk_pressure job, and sase disk reap all pass the shared disk.pressure warn threshold (the larger of 3 GiB and disk.pressure.warn_free_percent) as both the free-space floor and the recovery target, so pressure.min_available_bytes and pressure.recovery_available_bytes currently take effect only for direct reaper calls that leave those values unset.

Launched agents export CARGO_INCREMENTAL=0 by default, or CARGO_INCREMENTAL=1 when agent_cargo_incremental is enabled. Only hosts with the splitting rustc wrapper (athena) should opt in; elsewhere incremental test builds cost ~9 GB per run.

managed_tmp:
  horizons:
    command_scratch_seconds: 43200
    handoff_seconds: 259200
    build_scratch_seconds: 86400
    run_artifact_seconds: 1209600
  max_removals: 2000
  dead_launch:
    enabled: true
    grace_seconds: 7200
  pressure:
    max_bytes: 17179869184
    target_bytes: 8589934592
    min_available_bytes: 34359738368
    recovery_available_bytes: 51539607552
    min_age_seconds: 43200
    low_free_space_min_age_seconds: 3600
    min_entry_bytes: 67108864
  agent_cargo_incremental: false
Field Type Default Minimum Description
managed_tmp.horizons.command_scratch_seconds int 43200 0 Age horizon for scratch whose reader is the command that wrote it (editors, wrappers).
managed_tmp.horizons.handoff_seconds int 259200 0 Age horizon for files handed to a child process that may re-read them mid-run.
managed_tmp.horizons.build_scratch_seconds int 86400 0 Age horizon for Cargo and other build scratch created for one launched agent (non-procfs fallback; the dead-launch backstop reaps unheld launch scratch sooner).
managed_tmp.horizons.run_artifact_seconds int 1209600 0 Age horizon for run artifacts sase's TUI Agents tab reads back long after the run finished.
managed_tmp.max_removals int 2000 1 Removal budget for one reaper invocation, so a long-neglected root converges over passes.
managed_tmp.dead_launch.enabled bool true Run the dead-launch backstop pass over launch-keyed scratch no live process holds.
managed_tmp.dead_launch.grace_seconds int 7200 0 Quiet age before scratch no live process holds may be reaped by the backstop (and by liveness-aware pressure).
managed_tmp.pressure.max_bytes int 17179869184 0 Managed-root size that triggers pressure pruning of aged build scratch. 0 disables it.
managed_tmp.pressure.target_bytes int 8589934592 0 Managed-root size the pressure pass tries to return to.
managed_tmp.pressure.min_available_bytes int 34359738368 0 Filesystem free-space floor that also triggers pressure pruning. 0 disables it.
managed_tmp.pressure.recovery_available_bytes int 51539607552 0 Filesystem free-space target used after crossing the low-space floor.
managed_tmp.pressure.min_age_seconds int 43200 0 Minimum age before pressure can prune a large scratch entry.
managed_tmp.pressure.low_free_space_min_age_seconds int 3600 0 Emergency minimum age used instead when the free-space floor is breached, if lower than base.
managed_tmp.pressure.min_entry_bytes int 67108864 0 Small entries below this size do not participate in pressure pruning.
managed_tmp.agent_cargo_incremental bool false Incremental Cargo check/clippy for launched agents. Only hosts with the splitting rustc wrapper (athena) should set this true.

Source: src/sase/default_config.yml, src/sase/config/_settings_system.py, src/sase/core/managed_tmp_reaper.py

markdown

The column width SASE wraps generated Markdown prose at. It governs every Markdown surface SASE writes or renders itself: plan files, bead notes and pages, memory shims and the generated AGENTS.md/provider instruction files, generated skills, prompt archives, SDD documents, and the default --wrap for sase bead show and sase plan show. The value is resolved on each call, so an edit takes effect on the next command without restarting anything.

Values below the minimum, of the wrong type, or in a malformed config fall back to the shipped default rather than raising: a broken sase.yml must never turn sase plan propose into a traceback.

markdown:
  print_width: 88
Field Type Default Minimum Description
markdown.print_width int 88 20 Column width SASE wraps generated Markdown prose at.

The minimum of 20 is the floor below which SASE's display wrapper stops wrapping entirely, so a smaller value would silently do nothing.

Sharp edge: the prettier CLI cannot read this field. prettier (via just fmt-md, CI, or an editor integration) discovers its configuration from files on disk — a repo's package.json, .prettierrc, and friends — and those declarations mirror SASE's shipped default, not your effective configured value, so that a stock checkout is self-consistent for a contributor with no SASE config at all. If you configure a non-default markdown.print_width and then run sase init inside a repo whose prettier config still declares the default, the regenerated AGENTS.md will be wrapped at your width and that repo's fmt-md-check will fail on it. Change the repo's prettier config to match, or leave the field at its default.

pager

File-aware syntax highlighting for the SASE pager. Eligible raw sources — files, Markdown documents, diffs, and stdin with a convincing diff prefix — receive a muted language-aware overlay after first paint. Formatted cards, bead detail, commit manifests, and producer-styled ANSI stay unhighlighted.

pager:
  syntax: auto
Field Type Default Description
pager.syntax auto or never auto auto detects language from filename, trusted Markdown/diff, shebang, or stdin diff. never keeps unhighlighted rendering.

CLI --syntax overrides this field. --syntax none and --color never disable the added layer for the rest of the session, including followed targets. Unknown values fail schema validation (sase config / doctor diagnostics) and the runtime accessor falls back to auto so a hand-edited sase.yml cannot crash the pager.

timezone

The timezone that governs all SASE wall-clock display and timestamp generation (notifications, agent logs, artifact/agent-name timestamps, runtime durations, TUI displays, CLI tables, and generated Markdown pages). Columns or labels that previously carried a literal UTC suffix now render the configured zone abbreviation. When unset, SASE uses the host system timezone, so machines that don't share our timezone assumptions get sensible behavior out of the box.

timezone: "America/New_York" # default: system timezone
Field Type Default Description
timezone string system timezone IANA timezone name governing all SASE wall-clock display and timestamp generation.

chat_install

Configuration for chat-driven update workflows. External chat integrations can call sase.integrations.chat_install.start_chat_install_worker() to run the built-in sase update --json engine in a detached worker. The worker uses the same managed-vs-dev routing as the TUI Updates tab and the sase update CLI, so no custom update command is required.

chat_install:
  timeout_seconds: 900
  restart_attempts: 3
Field Type Default Description
chat_install.timeout_seconds int 900 Maximum runtime for sase update --json before returning exit code 124.
chat_install.restart_attempts int 3 Number of scheduler readiness polls after requesting its service proc start when the scheduler is not running after the update.

Only one chat update worker may run at a time; a lock under ~/.sase/chat_install/install.lock rejects concurrent starts. Worker output is written to timestamped logs under ~/.sase/chat_install/logs/. The configuration key and state paths remain named chat_install for compatibility. The old chat_install.command and chat_install.sync_workspace keys have been removed; delete them from user config if schema validation reports them. See docs/integrations.md for the integration-facing Python API.

Source: src/sase/default_config.yml, src/sase/integrations/chat_install.py

telegram

Custom Telegram slash commands are keyed by the bot command name. Define them in user configuration or an overlay; the Telegram integration deliberately ignores project-local configuration so a repository cannot add commands to your bot. Core SASE validates the definitions, and sase doctor checks that each command's executable resolves.

telegram:
  commands:
    projects:
      description: List enabled SASE projects.
      run: sase project list --state enabled
      output: message
      timeout: 60s
Field Type Default Description
telegram.commands.<name>.description string required Slash-menu description, from 1 to 256 characters.
telegram.commands.<name>.run string required Executable plus fixed arguments, parsed as an argument vector without a shell.
telegram.commands.<name>.output string message Deliver Markdown stdout as a message or rendered pdf.
telegram.commands.<name>.timeout string 60s Integer duration ending in s, m, or h.

Command names must contain 1–32 lowercase letters, digits, or underscores. The built-in names bead, beads, changes, fork, kill, list, update, and macros are reserved. The integration parses run as an argument vector and never invokes a shell. Text following /name is appended as one final argument, and the process runs from an isolated temporary directory, so use absolute paths or commands available on PATH rather than relying on a project working directory.

Run sase doctor -C integrations.telegram_commands after editing the map; unresolved command heads produce a warning with the affected names.

Source: src/sase/default_config.yml, src/sase/doctor/checks_integrations.py

tmux_agent

Configuration for tmux Agent: Launch Control's t binding and sase tmux-agent. Both surfaces share this block. A bad value is dropped with a warning rather than making the tmux key binding fail.

tmux_agent:
  # Base tmux window name. The first window is this name; later ones get a
  # numeric suffix (ai, ai2, ai3, ...).
  window_name: "ai"
  # Pass each agent CLI's approval-bypass flags (see the provider's
  # llm_interactive_cli descriptor). Per-provider overrides win.
  bypass_permissions: true
  # Reasoning effort applied to launches. "" follows llm_provider.default_effort;
  # "off" passes no effort flags at all.
  effort: ""
  # Run `clear` in the new window before starting the CLI.
  clear_screen: true
  # Optional shell command run after an agent CLI window closes, alongside
  # SASE's own window renumbering. Empty means nothing extra runs.
  after_close_command: ""
  # Per-provider overrides, keyed by registered provider name.
  providers:
    claude:
      enabled: true # false hides the provider from both surfaces
      key: "" # override the single-key menu shortcut
      model: "" # pin a model, e.g. "gemini-3.7-flash-high"
      effort: "" # per-provider effort; "" inherits, "off" disables
      args: [] # extra CLI args appended verbatim
      env: {} # environment variables for the new tmux window
      bypass_permissions: true # omit to inherit the global default
Field Type Default Description
tmux_agent.window_name string "ai" Base tmux window name. First window is this name; later ones get a numeric suffix (ai2, ai3).
tmux_agent.bypass_permissions bool true Pass each agent CLI's approval-bypass flags. The resolved command always shows whether bypass is on.
tmux_agent.effort string "" Effort applied to launches. "" follows llm_provider.default_effort; "off" passes no effort flags.
tmux_agent.clear_screen bool true Run clear in the new window before starting the CLI.
tmux_agent.after_close_command string "" Extra shell command run after an agent CLI window closes, alongside SASE's own window renumbering.
tmux_agent.providers.<name>.enabled bool true false hides the provider from both the tmux menu and sase's TUI panel.
tmux_agent.providers.<name>.key string "" Override the single-key menu shortcut. Must be exactly one printable non-whitespace character.
tmux_agent.providers.<name>.model string "" Pin a model (substituted into the provider's model_args).
tmux_agent.providers.<name>.effort string "" Per-provider effort; "" inherits the global tmux_agent.effort; "off" disables effort flags.
tmux_agent.providers.<name>.args list [] Extra CLI args appended verbatim after the resolved launch argv.
tmux_agent.providers.<name>.env mapping {} Environment variables for the new tmux window; user values win over the provider descriptor.
tmux_agent.providers.<name>.bypass_permissions bool inherit Omit to inherit tmux_agent.bypass_permissions. false launches without bypass args.

effort accepts "", "off", "none", "minimal", "low", "medium", "high", "xhigh", and "max". A config-default effort is best-effort: a provider that cannot honor the level launches without the flag rather than failing. An explicit sase tmux-agent -e <level> that the provider cannot honor is a usage error.

Three per-provider entries are load-bearing if you want the resolved argv to match the shell script this feature replaces:

tmux_agent:
  effort: "max"
  providers:
    claude: { env: { EDITOR: nvim } }
    codex: { effort: "xhigh" }
    grok: { effort: "xhigh" }
    opencode: { effort: "off" }
    agy: { model: "gemini-3.7-flash-high" }
    qwen: { model: "qwen3.6-plus" }
    muse: { model: "muse-spark-1.3" }
  • codex and grok cap out at xhigh. A config-default effort is best-effort, so effort_cli_args logs and skips max rather than downgrading it — without the per-provider xhigh they would launch with no effort flag at all, unlike the script.
  • opencode accepts every level as --variant <level>, so a global max would add a flag the script never passes; effort: "off" keeps it bare.
  • agy and qwen need no effort entry: both declare an empty supported-effort map, so the global max is skipped for them automatically and their argv already matches. Their model pins reproduce the script's hardcoded models.

EDITOR=nvim is a personal preference, not a provider requirement, which is why it lives on tmux_agent.providers.claude.env rather than in the Claude plugin.

Source: src/sase/default_config.yml, src/sase/config/tmux_agent.py

mobile_gateway

Configuration for sase mobile gateway start, which launches the workstation-hosted Rust gateway for paired mobile clients.

mobile_gateway:
  bind_address: "127.0.0.1"
  port: 7629
  state_dir: ""
  allow_non_loopback: false
  command: ""
  agent_bridge_command: ""
  helper_bridge_command: ""
  push_provider: "disabled"
  fcm_project_id: ""
  fcm_service_account_json: ""
  fcm_credential_env: ""
  fcm_dry_run: false
  push_timeout_seconds: 5
  push_retry_limit: 1
  startup_timeout_seconds: 10
Field Type Default Description
mobile_gateway.bind_address string "127.0.0.1" Host address to bind. Non-loopback values require explicit opt-in.
mobile_gateway.port int 7629 Gateway HTTP port.
mobile_gateway.state_dir string "" SASE state root for gateway storage. Empty uses the Rust gateway default.
mobile_gateway.allow_non_loopback bool false Allow LAN or tailnet binds after explicit user opt-in.
mobile_gateway.command string "" Gateway binary command override, parsed without a shell.
mobile_gateway.agent_bridge_command string "" Agent bridge command override passed to the gateway; empty uses its default.
mobile_gateway.helper_bridge_command string "" Helper bridge command override passed to the gateway; empty uses its default.
mobile_gateway.push_provider string "disabled" Push provider: disabled, test, or fcm.
mobile_gateway.fcm_project_id string "" Firebase project ID for FCM HTTP v1.
mobile_gateway.fcm_service_account_json string "" Local service-account JSON path. Do not commit this file.
mobile_gateway.fcm_credential_env string "" Env var containing an FCM bearer token or service-account JSON.
mobile_gateway.fcm_dry_run bool false Ask FCM to validate messages without delivering them.
mobile_gateway.push_timeout_seconds float 5 Timeout per push provider HTTP attempt.
mobile_gateway.push_retry_limit int 1 Retry attempts for best-effort push delivery.
mobile_gateway.startup_timeout_seconds float 10 Seconds to wait for gateway readiness before exiting.

Push payloads are hint-only and must not contain bearer tokens, pairing codes, prompt bodies, response text, attachment contents, attachment tokens, or host paths. Only credential paths or environment-variable names are placed on the gateway command line. See docs/mobile_gateway.md for setup examples and security notes.

Source: src/sase/default_config.yml, src/sase/integrations/mobile_gateway.py

sdd

Configuration for spec-driven development features, including prompt, tale, epic, research, and bead storage.

sdd:
  bead_refresh:
    mode: background
    ttl_seconds: 120
  repo:
    name: "" # provider-specific sidecar repo override
  push_after_commit: async
Field Type Default Description
sdd.bead_refresh.mode string background Sidecar bead-store freshness: background launches a TTL-gated managed sync after commands, blocking pulls before commands, and off disables remote refresh, including the live-bead-waiter hint the sidecar_auto_sync job marks and the equivalent hint in the runner's bead-wait fallback. Local dependency rechecks continue.
sdd.bead_refresh.ttl_seconds float 120 Minimum age of the last successful remote integration before another background worker is launched.
sdd.repo.name string "" Optional sidecar repo override for providers that support separate_repo; accepts name or owner/name. For GitHub, empty checks only <owner>/<repo>--sdd; set sdd.repo.name to use another repo such as sdd or owner/sdd.
sdd.push_after_commit bool or str async Controls git push after SDD commits in sidecar repositories: async, true, or false. Local commits are preserved.

The workspace provider owns storage selection. Built-in bare-git projects store SDD under sdd/. Managed GitHub projects use a --plans sidecar cloned at sase/repos/plans; every configured document role resolves at sase/repos/<role>. The default-seeded research role derives <owner>/<project>--research. Current managed initialization also records a --beads sidecar at sase/repos/beads. Unmigrated GitHub projects retain their provider-backed .sase/sdd/ clone. Materialized layouts record metadata in the primary workspace's .sase/sdd-store.json. Providerless projects fall back to a primary-workspace .sase/sdd/ store. The retired sdd.storage and sdd.version_controlled keys are ignored, stripped before validation, and reported by sase doctor for cleanup. See SDD Storage and Beads.

The default current layout has a schema-version 3 sidecar_repos record: every recorded role resolves to its role-specific clone, and bead state lives at the root of --beads. A record without a beads role remains schema version 2 and resolves bead state to beads/ in --plans. Ordinary resolution preserves that compatibility shape. Running managed sase repo init with the beads role enabled is the adoption step: it prepares the dedicated sidecar and writes a schema-version 3 record. A project that disables or otherwise omits the beads role stays on schema version 2. Initialization prepares configured sidecars in its current workspace and re-records stale compatibility metadata with the derived repository. Later workspaces clone lazy document roles on demand. The legacy single-sidecar shape continues to resolve byte-for-byte as before.

Built-in bare-git projects also auto-create or refresh generated SDD guide files during first-use #git:<project> initialization (target existing projects with their +<project> project tag), existing bare-repo registration, #git/workspace materialization, and the first in-tree SDD write. Setup/materialization flows commit and push only those generated init paths with an Initialize SDD init commit when needed.

For a repository whose own sase/sase.yml sets is_sase_managed: true, running sase repo init or its sase init repo alias writes managed entries for plans, beads, research, and agents, initializes configured sidecars, then refreshes generated guides and the directory map. On GitHub it derives the remotes as <owner>/<repo>--<role> for those four roles while honoring optional explicit repo pins. It initializes and pushes every enabled entry, then maintains the split store record; the agents role is not part of the SDD store record. Existing legacy --sdd files remain untouched locally and in their remote, while normal SDD routing uses the configured sidecars. --check previews provider and generated-file work without writing. Missing or false management markers make both forms successful no-ops; invalid local marker configuration fails before provider calls or writes.

Explicit initialization first performs authoritative provider discovery for every enabled sidecar. Each missing GitHub repository triggers a separate prompt naming its role and resolved repository; only y or yes authorizes that invocation to create it. The prompts are default-no and unavailable on non-interactive stdin. Bare sase init --yes cannot authorize repository creation: it reports a missing remote and defers creation to an interactive sase repo init without failing automated onboarding. --check remains network-free.

Source: src/sase/default_config.yml

bead

Configuration for the bead issue tracker.

bead:
  big_epic_phase_threshold: 5 # minimum authored phase count for llm_provider.big_epic_lander_model
  show:
    images: auto # image previews for sase bead show: auto, cells, kitty, or never
  task_triage:
    min_plus_ones: 1 # +1 reports a ready untyped/undeclared task needs before it earns a TaskTriage gate
    stale_after_days: 7 # age at which a still-sub-threshold ready task bead is stale
    stale_cleanup_min_beads: 10 # stale beads required before bead_stale_cleanup gates
  task_types: [] # optional catalog overrides and project-local types
  push_after_commit: true # compatibility field; current bead-work launches do not consult it

| Field | Type | Default | Description | | ---------------------------------------------- | ----------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --- | | bead.big_epic_phase_threshold | int | 5 | Minimum total authored phase count that selects llm_provider.big_epic_lander_model for an epic without an explicit land model. Must be at least 1; malformed runtime values defensively fall back to 5. | | bead.attachments.sensitive_patterns | list[str] | [] | Extra glob patterns treated as sensitive by the bead note attachment authoring service, additive with the built-in policy. A note that references a matching path fails unless -S/--allow-sensitive is passed. Malformed values fail open to []. | | bead.attachments.git_max_bytes | int | 52428800 | Largest single attachment the git tier accepts, in bytes (50 MiB). Missing, malformed, or above 95 MiB fails open to the default. | | bead.attachments.public_max_bytes | int | 26214400 | Largest attachment the public tier accepts, in bytes (25 MiB). Anything larger stays on the private tiers. Missing, malformed, or above 95 MiB fails open to the default. | | bead.attachments.require_upload | bool | false | When true, attachment bytes upload before the bead event is written; any upload failure aborts with nothing written and nothing queued. Malformed values fail open to false. | | bead.attachments.auto_fetch_max_bytes | int | 26214400 | Largest single attachment fetched automatically by read/show, in bytes (25 MiB). Missing or malformed values fail open to the default. -d/--download lifts the cap for one invocation; attachment path always fetches. | | bead.attachments.background_upload_min_bytes | int | 67108864 | Objects at or above this size upload in the background (64 MiB) through a detached drain worker. Missing or malformed values fail open to the default. require_upload: true stays synchronous instead. | | bead.attachments.large_store | object/null | null | Optional rclone large-object tier for objects above git_max_bytes: {remote: "<rclone remote:path>", max_bytes: 2147483648} (default cap 2 GiB). Null disables the tier. Missing, malformed, or remote-less values fail open to null. | | | bead.attachments.local_cache_max_bytes | int | 10737418240 | Local attachment cache budget in bytes (10 GiB). sase bead attachment prune evicts cached objects confirmed present in a shared store, oldest views first, to fit under this budget. Missing or malformed values fail open to the default. | | | bead.show.images | string | auto | Image previews for sase bead show: auto, cells, kitty, or never. read, JSON, and piped show never draw. Malformed values fail open to auto. | | bead.task_triage.min_plus_ones | int | 1 | Fallback +1 bar, applied only to untyped legacy beads and to types that declare no triage.min_plus_ones of their own. A typed bead uses its own spec bar instead, which is why this default does not describe most task beads: flake ships as 3, bug ships as 1, and ci, feature, and memory ship as 0. See Task Types for how a bead resolves its bar. Must be at least 0. Suppression withholds only the gate — a sub-threshold bead stays stored as ready, and a gate already raised for a bead that falls below the bar is canceled and its notification dismissed. | | bead.task_types | list | [] | Project catalog entries. {use: <plugin>@<slug>, ...} deep-merges sibling keys onto an installed type; a full spec without use: defines a new slug and may not shadow a builtin. | | bead.task_triage.stale_after_days | int | 7 | Days after creation at which a still-sub-threshold ready task bead is considered stale and eligible for the bead_stale_cleanup gate. Must be at least 1. | | bead.task_triage.stale_cleanup_min_beads | int | 10 | Stale beads required across all enabled projects before bead_stale_cleanup raises its gate; below this count the job does nothing. Must be at least 1. | | bead.push_after_commit | bool or str | true | Retained in the accepted configuration shape, but the current sase bead work path does not read it. Without --no-push, bead-ID launches synchronously run managed sync even for an in-tree Git store; a remote-backed detached store additionally requires an actual pre-spawn push. |

Below the threshold, an epic land agent uses llm_provider.epic_lander_model. At or above it, it uses llm_provider.big_epic_lander_model instead. An explicit land model remains authoritative over both.

See bead_task_triage for how the task_triage fields gate TaskTriage notifications, Task Triage Notification for the post-upgrade dismissal of already-raised sub-threshold gates, Discovered Follow-Up Capture and Triage for the human-facing triage lifecycle.

In Launch Control (,m), the big epic starts at row shows this effective threshold next to the two epic-lander rows. e or Enter opens a focused positive-integer editor, and r previews an unset reset against bead.big_epic_phase_threshold in the writable user-base config or its chezmoi source. There is no temporary override for this setting; pressing o or x on the row only reports that Edit/Reset are available.

See docs/beads.md for the current pre-spawn checkpoint and publication flow.

Source: src/sase/default_config.yml

external_mirror

Configuration for the external tracker mirror. See External Issue Mirroring and the external_mirror lane's external_issue_mirror and external_pr_mirror jobs, the first production use of for_each: {source: projects} fan-out.

One shared filter surface governs which tracker issues become beads (external_mirror.issues.filters) and which remote pull requests become Patches (external_mirror.pull_requests.filters). Each criterion is either a *_globs list (accepting !-prefixed exclusions, matched like file_hooks's path_globs) or a states enum list. A record matches a criterion if it matches any positive glob (or the criterion has no positive globs) and matches no negative glob; a record is mirrored only when every criterion accepts it, and matching is case-folded. Setting a criterion replaces the shipped defaults for that criterion rather than appending to them. Filters gate creation only: a record a filter now excludes keeps whatever bead or Patch it already has, the mirror never deletes.

external_mirror:
  issues:
    filters:
      author_globs: []
      label_globs: []
      title_globs: []
      states: []
  pull_requests:
    filters:
      author_globs: []
      base_ref_globs: []
      head_ref_globs:
        - "!release-please--*"
        - "!release-please/*"
        - "!release-plz-*"
        - "!release-plz/*"
      title_globs: []
      states: []

Pull requests ship with the four head_ref_globs exclusions above, so release-please and release-plz PRs stop becoming Patches by default. Head ref is the key those defaults filter on rather than author or title: release automation can author as a human account on some repos, and PR titles are user-configurable, but the release branch name is stable.

Field Type Default Description
external_mirror.issues.filters.author_globs list of str [] Issue author globs, including !-prefixed exclusions.
external_mirror.issues.filters.label_globs list of str [] Issue label globs, matched against every label on the issue.
external_mirror.issues.filters.title_globs list of str [] Issue title globs.
external_mirror.issues.filters.states list of str [] Issue states (open/closed) accepted for mirroring.
external_mirror.pull_requests.filters.author_globs list of str [] Remote PR author globs.
external_mirror.pull_requests.filters.base_ref_globs list of str [] Remote PR base-ref globs.
external_mirror.pull_requests.filters.head_ref_globs list of str ["!release-please--*", "!release-please/*", "!release-plz-*", "!release-plz/*"] Remote PR head-ref globs.
external_mirror.pull_requests.filters.title_globs list of str [] Remote PR title globs.
external_mirror.pull_requests.filters.states list of str [] Remote PR states (open/closed) accepted for adoption.

external_mirror.exclude_labels and external_mirror.pr_authors are deprecated aliases for external_mirror.issues.filters.label_globs (folded in as negated globs) and external_mirror.pull_requests.filters.author_globs (folded in as plain globs), respectively. The fold applies only when the modern criterion is empty — a non-empty modern criterion always wins, and the legacy key's value is then ignored. sase doctor -C config.external_mirror flags both a set legacy key and the "set alongside a non-empty modern criterion" case.

Records a filter drops are visible rather than silently missing: sase patch sync-external's table gets a Filtered column, sase bead sync-external's per-project line gains filtered=<n> when non-zero, and the Patches pane's project banners show a · M remote-only suffix when a filtered-PR count is known.

Source: src/sase/default_config.yml

feature_flags

feature_flags is the portable user and project override surface for code-owned SASE feature flags. Registry defaults live in src/sase/feature_flags/registry.py; the config block only overrides registered keys. It remains valid and lower-precedence than a saved machine preference. The Config Flags pane and sase flag enable / sase flag disable never edit this block, overlays, project-local sase.yml, or chezmoi source.

feature_flags:
  ref_sync_gesture: true

The generated JSON Schema exposes one boolean property per registered flag with its description and default. Unknown keys are tolerated by the schema so downgraded installs can still read a config written by a newer SASE, but the resolver warns and ignores unknown keys at runtime.

beta flags default off and gate work that is still landing; sunset flags default on and keep a fallback path reachable until the flag is removed. The schema marks sunset flags deprecated. The currently registered flags are the rows below, plus the sunset flag for retired macro spellings. That flag defaults on. Its registry key uses the retired spelling, so its behavior is documented in the macro rename section instead of this table.

Flag Kind Default Controls
ace_refresh_tokens sunset true sase's TUI and proc refreshes are gated on per-surface, stat-only change tokens.
admin_center_flags sunset true The Admin Center Config catalog shows the Flags pane.
agent_sudo_requests beta false The typed sudo request workflow (sase sudo) and its review modal.
agents_session_manifest_compat sunset true When an owner manifest still lists files, accept the current set or the legacy set that drops sessions/ pages. See Strict v2 layout.
agents_unified_query sunset true The Agents tab filter uses the shared agents-live boolean query profile.
autonomy_record_only sunset true Only agent_meta.autonomy carries %auto state; off also writes the legacy meta keys.
axe_routine_job_contract sunset true AXE configuration projections and public JSON use routine/job names; see axe.
bgcmd_legacy_slots sunset true Legacy ~/.sase/axe/bgcmd slot directories stay readable in the Services tab oneshot section.
claude_helper_channel sunset true Claude receives the packaged helper instructions and a tool guard that prevents native helpers from submitting root final declarations. See Root and helper agents.
grok_rules_delivery sunset true Grok root runs receive the SASE single-turn directive plus the managed project root AGENTS.md through --rules. See Grok Build Integration.
instruction_shadow_render sunset true Root provider invocations record an intended bundle and manifest and set SASE_INSTRUCTIONS_FILE for the call. Current providers do not read it, so existing delivery stays the same. See Instruction Bundles.
legacy_agent_family_syntax sunset true Retired agent-family spellings still alias agent-session spellings. Off rejects new uses; stored records still read. See Agent sessions.
legacy_sase_shell_syntax sunset true Retired sase-shell spellings still alias sase-turn spellings. Off rejects new uses; stored records still read. See Gate turns.
monitor_continuation_records sunset true New monitors persist versioned continuation records, frozen outcome policy, and durable delivery state.
muse_synchronous_shell sunset true muse exec runs with --enable-shell-tool, so Muse runs commands synchronously; see Muse Code Integration.
provider_drain beta false A hard provider disable relaunches stranded agents through sase agent drain (see llm_provider.usage_limit and Launch Control's automatic provider drain in ace.md).
queue_capacity_budget sunset true %queue(capacity=N) is the launch's own admission budget; see max_running_agents.
ref_sync_gesture sunset true Typing a second : after an empty @<kind>: refreshes that kind's sidecar and reopens the payload menu.
refresh_panel sunset true r on Agents and R elsewhere open the Refresh panel, and ,y opens it on Full history.
slim_agents_manifest sunset true Agents-sidecar owner manifests omit each hood's per-hood file list.
strict_macro_input_types sunset true Unknown macro input types are load errors with suggestions. Off keeps the legacy fallback to the single-line line type. See Typed Inputs.
typed_launch_units beta false Typed launch units, %if:: script admission, and %proc (see below).

agent_decks and agent_tabs are no longer registered. The Agents tab always shows data decks and cards and agent tabs. A saved preference for a flag that is no longer registered is removed the next time an installing process reconciles feature_flags.json.

legacy_agent_family_syntax accepts, while it stays on, the retired family keyword on %id as session=, the family and kind agent-query values as session: and kind:session, the --next-fork family value, a gate "fork": "family", and SASE_AGENT_FAMILY_ATTACH. legacy_sase_shell_syntax accepts sase gate create --shell, --shell-status, --shell-stop-status, and the --next-fork shell value; a gate spec's "shell" block, the "fork" key set to "shell", and "continuation_mode": "gate_shell"; --shell on sase proc list or sase proc run (replaced by --name); and the retired gate.shell .reclaim_grace_seconds key. Supplying both names for the same option or block is an error in either flag state. Write the session and turn spellings in new prompts and config, and use --name for proc commands.

The sunset flag for retired macro spellings accepts, while it stays on, the aliases in the macro rename section. Turning it off rejects those spellings in a new command, config key, frontmatter key, environment variable, or sase path target, and the error names the replacement. The retired entry-point group is not loaded, a plugin's packaged copy of the retired definition directory is not read, and those retired directories are invisible to expansion, workflow loading, completion, catalogs, and save choices. Config keys, frontmatter keys, and the plugin-disable environment variable are an error when both the retired name and the macro name are present, in either flag state. The LSP command environment variable is the exception: a non-empty macro value wins and the retired value is ignored. See that section for which names those are. The region alias there stays accepted either way. State files and agent artifact filenames already written under the old names stay readable either way, and new writes use only the macro spelling. A default sase doctor run includes the config check for those retired authored surfaces in either flag state. It reports non-empty retired definition directories and does not report those state files, agent artifact filenames, or that region alias.

agents_session_manifest_compat applies when an owner-manifest hood entry still carries an explicit files list. Readers accept a missing list either way. On, a present list may be the current canonical set or the legacy set, which is that current set with every sessions/ path removed and the legacy redirect stubs retained. Off, only the current canonical set matches. See Strict v2 layout.

Run sase flag list for the live registry with effective and saved state.

The registered typed_launch_units beta flag defaults to false. Enabling it exposes the experimental %if:: script-admission and %proc parser, completion, and launch-plan contract. Static %if(should_run=true|false) segment omission is not feature-gated and works before typed launch planning in either flag state. User-initiated sase's TUI and sase run submissions execute typed directives through durable typed admission without a LaunchApproval gate. The frozen plan keeps the complete %id and %clan identity binding, and keyed {@<id>} markers resolve once at batch creation. Agent-initiated launches still freeze the typed plan for LaunchApproval; after approval, the same admission coordinator resolves waits, evaluates %if:: predicates, and dispatches eligible units — agent units through the established agent launch path, and %proc units through native stand-alone named-proc dispatch; see Experimental typed launch units.

Saved machine preferences

Persistent enable/disable choices live in a SASE-owned machine-state file $SASE_HOME/feature_flags.json (normally ~/.sase/feature_flags.json), not in configuration. The file is a bounded JSON object of exact booleans keyed by registered flag names; equal-to-default values are still stored because enable and disable are durable user choices. Inspect the path with sase flag show <key> or the Flags pane detail card (STATE).

{
  "version": 1,
  "flags": {
    "admin_center_flags": true,
    "ref_sync_gesture": false
  }
}

Missing state is an empty snapshot (every flag uses its registry/config default). A malformed, oversized, or unsupported whole file is not silently deleted or overwritten: reads return no preferences plus a diagnostic, mutations fail with the path and a recovery-oriented message, and registered flags still resolve from lower layers.

Valid unknown keys in the machine-state file are tolerated by registry-neutral reads and writes, but installing process-wide feature flags for sase's TUI, AXE, or an agent runner reconciles that file against the running SASE registry and removes saved entries that are no longer registered. sase's TUI defers the disk transaction until the initially visible surface is ready, then shows one cleanup toast; AXE and agent runners report the same cleanup on stderr. Clean starts do not rewrite the file or emit cleanup prose. If another process already removed the stale keys, sase's TUI reports that the previously unregistered saved keys are already absent. If cleanup fails or the file becomes unusable, SASE preserves the file, keeps the diagnostic, and retries on the next startup.

This means downgrading or misspelling a saved flag no longer preserves that saved key past the next installing process. A later upgrade falls back to registry/config defaults until the choice is saved again. Portable YAML feature_flags entries keep the older tolerance: unknown config keys are diagnosed and ignored, not rewritten.

Resolution order

Effective value is resolved in this order, later sources winning:

registry default
  < user config
  < overlay config
  < saved machine preference (SAVED)
  < explicit in-process/test override
  < SASE_FEATURE_FLAGS transport / legacy env
  < root CLI -f/--enable-feature or -F/--disable-feature

Plugin config layers never flip first-party flag defaults. A local-config entry for any feature flag is ignored with a scope_violation warning: a feature flag cannot be set from the local config layer, because sase's TUI disables project-local config and flags must resolve consistently across frontends.

The Flags pane and sase flag list / show report effective state and saved state separately. Provenance SAVED means the machine-state file won. If environment or root CLI overrides still shadow the saved choice, the UI shows a “forced for this process” warning rather than pretending the toggle already controls this process. Saving still restarts sase's TUI and the service host; the saved value takes effect once that higher source is removed.

Root-level -f/--enable-feature and -F/--disable-feature force a registered flag on or off for one sase invocation. They must appear before the subcommand (sase -f ref_sync_gesture run "..."). They outrank every config layer, a saved machine preference, and an inherited SASE_FEATURE_FLAGS value, and they merge into SASE_FEATURE_FLAGS so launched agents and other child processes inherit the same overrides.

SASE_FEATURE_FLAGS is a strict JSON object of booleans, for example {"ref_sync_gesture":false}. Malformed JSON, a non-object payload, or a non-boolean value is a startup error for that process. SASE-launched children inherit a resolved snapshot through the same variable, so sase flag list marks env provenance prominently. CLI overrides are marked the same way (CLI:--enable-feature / CLI:--disable-feature). After a successful save, SASE merges the chosen key into SASE_FEATURE_FLAGS so an sase's TUI execv restart does not inherit the old pinned snapshot above the new saved value.

Enable, disable, and restart

sase flag enable <flag>
sase flag disable <flag>

Both commands persist the choice in the machine-state file and, on success, restart the scheduler service proc when it is already running. A stopped scheduler is left stopped. They never start a proc the user had stopped, and they never signal a sase's TUI session in another terminal — restart any separately running sase's TUI yourself. Repeating an already-saved enable or disable is idempotent for the store but still retries that scheduler restart. A restart failure does not roll back the saved preference: rich and JSON output distinguish mutation from restart so a partial success is safe to retry. Unknown flags are usage errors (exit 2); store or restart failures use exit 1. --json emits one versioned document with separate mutation and restart objects.

From Config > Flags, a confirmed toggle uses the same mutation path, then waits for TUI-local tasks and installation changes and performs one controlled sase's TUI and service host restart. Independent commands keep running. Disabling admin_center_flags from its own row is supported: the pane disappears after restart, and sase flag enable admin_center_flags restores it. The CLI commands are not gated by that flag.

Create temporary flags with sase flag new <key> rather than editing the registry by hand. The command creates a task bead of type flag, prints the registry entry, and gives the both-states test checklist. Kinds are beta (default off) and sunset (default on); the registry default is derived from the kind. See Beads for the removal lifecycle.

Source: src/sase/feature_flags/registry.py, src/sase/feature_flags/schema.py, src/sase/feature_flags/snapshot.py, src/sase/feature_flags/state.py

workspace

Controls how SASE chooses the physical location of managed workspace checkouts. See docs/workspace.md for the directory-layout reference and CLI workflows.

workspace:
  root: xdg-state # "xdg-state", "adjacent", or an absolute path
  project_key: "" # explicit project-key override; empty = derive from git remote / primary path
  share_git_objects: true # borrow primary Git objects for numbered managed checkouts
  cleanup_ttl_days: 14 # age threshold for `sase workspace cleanup --stale`
  held_claim_ttl_days: 14 # age threshold for releasing pinned dead workspace claims
Field Type Default Description
workspace.root string "xdg-state" Root policy. "xdg-state" uses the platform state dir; "adjacent" keeps the legacy <primary>_<num>/ layout as an explicit opt-in; an absolute path is used as the managed-root base. SASE_WORKSPACE_ROOT overrides this base directory.
workspace.project_key string "" Override the per-project namespace under managed roots. Empty derives a stable key from a single git remote slug or the primary-path basename plus a short hash.
workspace.share_git_objects bool true Borrow the primary checkout's Git object database for numbered managed clones. Disable for standalone clones; sase workspace repair dissociates existing SASE-managed borrowers while preserving non-SASE alternate entries.
workspace.cleanup_ttl_days int 14 Minimum age (in days) of an unclaimed managed checkout before sase workspace cleanup --stale will remove it.
workspace.held_claim_ttl_days int 14 Minimum age (in days) of a pinned dead-PID workspace claim before the stale sweep releases it. Artifacts are kept so the failed run stays dismissible in sase's TUI; only the workspace hold is dropped. 0 disables age-based release.

Platform defaults for the xdg-state policy:

Platform Managed root
Linux $XDG_STATE_HOME/sase/workspaces (falls back to ~/.local/state/sase/workspaces)
macOS ~/Library/Application Support/sase/workspaces
Windows %LOCALAPPDATA%\sase\workspaces

Numeric identity is the same on every root policy: #0 is the primary checkout, #1–#9 are reserved, and managed claim workspaces start at #10. See docs/workspace.md for the full identity model and backup/container/NFS caveats.

For non-adjacent policies, physical checkouts live under <managed-root>/<project_key>/<project>_<num>/. For example, workspace.root: /mnt/sase-workspaces with project key github.com_org_repo places workspace #10 at /mnt/sase-workspaces/github.com_org_repo/<project>_10/. When SASE_WORKSPACE_ROOT is set, it supplies the same <managed-root> base for the process.

Existing adjacent checkouts are not moved automatically by the default. Run sase workspace migrate --to xdg-state to carry legacy <primary>_<num>/ directories into the managed root, or set workspace.root: adjacent explicitly to keep the old sibling layout.

sase repo open <primary-repo> -w NUM -r "<reason>" is an explicit preparation command for a checkout you plan to use outside a normal sase run launch. It uses the same root policy when it materializes the checkout, backs up uncommitted local changes through the active VCS provider, cleans the checkout, checks out and syncs the provider default parent revision, and prints the resulting path. For manual scratch work, choose a claim-range number such as 10; #0 is the primary checkout and #1 through #9 are reserved compatibility numbers.

Source: src/sase/default_config.yml, src/sase/workspace_provider/store.py

telemetry

Configures local telemetry recording and retention. See docs/telemetry.md for the full telemetry reference, including the CLI, metric catalog, local store, and Admin Center tab.

telemetry:
  enabled: true
  flush_interval_seconds: 15
  retention:
    raw_seconds: 172800
    rollup_5m_seconds: 2592000
    rollup_1h_seconds: 31536000
  health_thresholds:
    error_rate_warn: 10.0
    error_rate_critical: 25.0
    retry_rate_warn: 10.0
    retry_rate_critical: 25.0
    p95_latency_warn: 300.0
    p95_latency_critical: 600.0
Field Type Default Description
telemetry.enabled bool true Enable or disable local telemetry recording.
telemetry.flush_interval_seconds float 15 Flush interval for long-lived processes.
telemetry.retention.raw_seconds int 172800 Retain raw samples for 48 hours.
telemetry.retention.rollup_5m_seconds int 2592000 Retain five-minute rollups for 30 days.
telemetry.retention.rollup_1h_seconds int 31536000 Retain hourly rollups for one year.
telemetry.health_thresholds.error_rate_warn float 10.0 Error rate % threshold for WARN health status.
telemetry.health_thresholds.error_rate_critical float 25.0 Error rate % threshold for CRITICAL status.
telemetry.health_thresholds.retry_rate_warn float 10.0 Retry rate % threshold for WARN health status.
telemetry.health_thresholds.retry_rate_critical float 25.0 Retry rate % threshold for CRITICAL status.
telemetry.health_thresholds.p95_latency_warn float 300.0 P95 latency threshold (seconds) for WARN status.
telemetry.health_thresholds.p95_latency_critical float 600.0 P95 latency threshold (seconds) for CRITICAL.

The legacy telemetry.prometheus block (url, pushgateway_url, exposition_port) is still schema-valid for compatibility but is ignored.

Source: src/sase/default_config.yml, src/sase/telemetry/_config.py

tool_runs

Operational retention for the machine-local ToolRun ledger. This block uses ordinary config-layer precedence (builtin, plugin, user, overlay, then project). It is independent of the project-owned tools catalog: changing retention cannot change argv.

tool_runs:
  summary_days: 180
  detail_days: 60
  log_days: 14
  log_max_bytes: 2147483648
  run_log_max_bytes: 268435456
  event_max_bytes: 16777216
  soft_ceiling:
    default: ""
    providers: {}
Field Type Default Minimum Description
tool_runs.summary_days int 180 1 Days to retain run/attempt/lifecycle summaries after settlement.
tool_runs.detail_days int 60 1 Days to retain stage/sample events and projections. Must be <= summary_days.
tool_runs.log_days int 14 1 Days to retain output and event files after settlement. Must be <= detail_days.
tool_runs.log_max_bytes int 2147483648 1 Aggregate retained-log target in bytes (2 GiB).
tool_runs.run_log_max_bytes int 268435456 1 Per-run retained-log cap in bytes (256 MiB).
tool_runs.event_max_bytes int 16777216 1 Per-run event-file cap in bytes (16 MiB).
tool_runs.soft_ceiling.default string "" — Default soft-ceiling duration (90s, 20m, 1h); empty means none.
tool_runs.soft_ceiling.providers.<name> string "" — Per-provider soft-ceiling duration keyed by registered provider name; empty means none.

tool_runs.soft_ceiling is the most time an agent should block on one sase tool run before escalating to a monitor; it kills nothing; the effective budget is the smaller of this and the hard ceiling's budget. The provider entry wins, then default, then none. A malformed or non-positive value is ignored with one warning and never fails a launch.

Unknown fields and non-positive or inconsistent horizons are rejected. JSON Schema cannot express detail_days <= summary_days; runtime validation covers that.

Source: src/sase/default_config.yml, src/sase/config/sase.schema.json, src/sase/config/tools.py

tools

Named tool catalogs are project-owned complete entries in sase/sase.yml. SASE does not read argv from the recursively merged config: tools: in builtin, plugin, user, or machine layers is diagnosed and ignored so list concatenation and deep merge cannot change execution identity. Malformed project YAML or catalog fields are actionable errors, not an empty catalog.

tools:
  check:
    argv: [just, check]
    description: Run the repository scoped check.
    stages: run_silent
    inputs: [Justfile]
    env: [SASE_PYTEST_WORKERS]
    args: deny
    fingerprint:
      toolchain:
        python: [python, --version]
Field Type Default Description
tools.<name>.argv string array required Exact argv run at the project root. Not expanded for shell syntax or environment variables.
tools.<name>.description string "" Human-readable purpose shown by sase tool list.
tools.<name>.stages run_silent | none none Whether run_silent stage producers attach to this tool.
tools.<name>.inputs string array [] Repository-relative glob patterns observed as fingerprint inputs.
tools.<name>.env string array [] Allow-listed environment variable names observed as fingerprint inputs.
tools.<name>.args allow | deny deny Whether extra arguments after -- may be appended to argv.
tools.<name>.duration_class short | long | unbounded short (when unset) Duration-class floor for inline-vs-monitor routing. Validated by the Rust core and excluded from the definition digest.
tools.<name>.fingerprint.repos string array [] Configured repository identities; empty means the current repository.
tools.<name>.fingerprint.toolchain map of argv arrays {} Bounded toolchain probes.

sase tool list reports LAST (newest native result for this project and definition) and TYPICAL (median duration of up to 30 normally exited native runs in 30 days). Missing samples render as an em dash, never as zero or an ETA. It also reports CLASS (the effective duration class, short when unset) and calibrates each declaration against the corpus (see Duration classes).

Source: src/sase/config/sase.schema.json, src/sase/config/tools.py, src/sase/main/tool_handler.py

update

Configures install-mode switching (see Install mode switching).

update:
  dev_root: "~/projects/github"
Field Type Default Description
update.dev_root str ~/projects/github Base directory for dev-mode editable checkouts, materialized owner-nested as <dev_root>/<owner>/<repo>.

Source: src/sase/default_config.yml, src/sase/mode_switch/repos.py

Environment Variables

LLM Provider

Variable Description
SASE_MODEL_TIER_OVERRIDE Force all LLM invocations to a specific tier (large or small).
SASE_MODEL_SIZE_OVERRIDE Legacy alias for SASE_MODEL_TIER_OVERRIDE (big or little).
SASE_LLM_EXEC_PROVIDER Execute through this registered provider while preserving requested model metadata.
SASE_LLM_LARGE_ARGS Extra CLI args appended for large tier invocations (any provider).
SASE_LLM_SMALL_ARGS Extra CLI args appended for small tier invocations (any provider).
SASE_CLAUDE_LARGE_ARGS Claude-specific extra args for large tier (fallback if generic unset).
SASE_CLAUDE_SMALL_ARGS Claude-specific extra args for small tier (fallback if generic unset).
SASE_CLAUDE_MAX_WAIT_CONTINUATIONS Claude single-turn wait guard cap (default: 2).
SASE_CODEX_PATH Path to the Codex CLI binary (default: PATH lookup, then NVM_BIN/codex).
SASE_CODEX_LARGE_ARGS Codex-specific extra args for large tier (fallback if generic unset).
SASE_CODEX_SMALL_ARGS Codex-specific extra args for small tier (fallback if generic unset).
SASE_CODEX_DISABLE_SHADOW_HOME Set to 1 to launch Codex with the inherited CODEX_HOME.
SASE_QWEN_PATH Path to the Qwen Code CLI binary (default: qwen).
SASE_QWEN_LARGE_ARGS Qwen-specific extra args for large tier (fallback if generic unset).
SASE_QWEN_SMALL_ARGS Qwen-specific extra args for small tier (fallback if generic unset).
SASE_OPENCODE_PATH Path to the OpenCode CLI binary (default: opencode).
SASE_OPENCODE_LARGE_ARGS OpenCode-specific extra args for large tier (fallback if generic unset).
SASE_OPENCODE_SMALL_ARGS OpenCode-specific extra args for small tier (fallback if generic unset).
SASE_AGY_PATH Path to the Antigravity CLI binary (default: agy).
SASE_AGY_PRINT_TIMEOUT Override the agy --print-timeout Go duration (default: 24h).
SASE_AGY_MAX_NO_PROGRESS_CONTINUATIONS Override the no-progress continuation cap (default: 2).
SASE_AGY_LARGE_ARGS Antigravity-specific extra args for large tier (fallback if generic unset).
SASE_AGY_SMALL_ARGS Antigravity-specific extra args for small tier (fallback if generic unset).
SASE_MUSE_PATH Path to the Muse Code CLI binary (default: muse).
SASE_MUSE_LARGE_ARGS Muse-specific extra args for large tier (fallback if generic unset).
SASE_MUSE_SMALL_ARGS Muse-specific extra args for small tier (fallback if generic unset).
SASE_MUSE_SANDBOX Set to on to keep Muse's sandbox with --sandbox-network enabled.
SASE_GROK_PATH Path to the Grok Build CLI binary (default: grok).
SASE_GROK_LARGE_ARGS Grok-specific extra args for large tier (fallback if generic unset).
SASE_GROK_SMALL_ARGS Grok-specific extra args for small tier (fallback if generic unset).
SASE_CONTINUATION_BUDGET_ENFORCE Set to 1 to apply continuation-budget preflight outside monitor successors.
SASE_CONTINUATION_CONTEXT_LIMIT_BYTES Positive context-limit override for continuation preflight (default: 800000).
SASE_CONTINUATION_TRANSPORT_LIMIT_BYTES Optional positive transport-limit override for continuation preflight.
SASE_CONTINUATION_CHECKPOINT_THRESHOLD_BYTES Optional positive checkpoint-threshold override.
SASE_CONTINUATION_INSTRUCTION_RESERVE_BYTES Positive instruction-reserve override (default: 0).
SASE_CONTINUATION_TOOL_RESERVE_BYTES Positive tool-context reserve override (default: 0).
SASE_CONTINUATION_OUTPUT_RESERVE_BYTES Positive output reserve override (default: 0).
SASE_CONTINUATION_REASONING_RESERVE_BYTES Positive reasoning reserve override (default: 0).

For the per-provider args, the generic SASE_LLM_*_ARGS variables are checked first. If unset, the provider-specific variable is used as a fallback. Values are split on whitespace and appended to the CLI command.

Monitor successors always run the continuation-budget preflight; the explicit enforce variable is for another invocation path that needs the same guard. The preflight measures the final expanded prompt after replay. When essential content exceeds the effective budget, SASE records the decision and refuses before calling the provider. Each SASE_CONTINUATION_*_BYTES variable outranks the matching llm_provider.continuation_budget setting, including provider and model overrides. Invalid, zero, or negative numeric overrides fall back to the corresponding default or remain unset.

SASE-launched Codex subprocesses use a disposable shadow CODEX_HOME by default. The shadow home is created under ~/.cache/sase/codex_home/, receives a copy of the real config.toml, symlinks other Codex home entries back to the real home, and is removed when the subprocess exits. If the real Codex home does not provide AGENTS.override.md or AGENTS.md, SASE also links ~/AGENTS.md into the shadow as Codex's $CODEX_HOME/AGENTS.md fallback. This prevents Codex runtime config rewrites from dirtying the user-managed Codex config while preserving auth, hooks, skills, logs, and caches.

Qwen Code uses qwen --input-format text --output-format stream-json --yolo --model <model> and expects users to configure Qwen auth through Qwen's supported settings path. Qwen OAuth free tier access ended on 2026-04-15; use API keys, Alibaba Cloud Coding Plan, OpenRouter, Fireworks, or another Qwen-supported provider.

OpenCode uses opencode run --format json --dangerously-skip-permissions --model <provider/model> --dir <cwd> <prompt> and expects users to configure OpenCode auth/settings through its normal XDG paths. OpenCode model names usually include a provider prefix; use opencode models to list models in your configured environment.

Muse Code uses muse exec --json --workspace <cwd> --model <model> --trust-workspace --disable-approval --disable-sandbox --user-input-auto-resolve --no-foreign-personal-context --session-id <uuid> --prompt-file <tempfile> and expects users to authenticate with muse login or META_API_KEY. SASE always sets MUSE_NO_AUTO_UPDATE=1 for agent runs so Muse's launcher cannot replace its binary mid-run; update Muse with sase agent-cli update muse instead. Muse's sandbox makes .git read-only inside the workspace, which would break in-run commits, so SASE disables it by default; SASE_MUSE_SANDBOX=on keeps the sandbox with --sandbox-network enabled at the documented cost of in-run commits failing.

Muse's Contributor models carry a model advisory: Meta uses their inputs and outputs to train and improve Meta's AI models (see the generated Built-in Model Catalog for the current names). SASE keeps them fully reachable by name and never points a provider tier map at either. Whichever shipped size aliases currently include a Contributor member route there automatically whenever a muse executable is available (see the generated shipped size-alias defaults); override llm_provider.model_aliases.builtin.<size> to opt out. The advisory renders in sase's TUI model picker, in %model completion detail, and in the resolved model label, and sase doctor -C llm.model_advisory warns when a configured default or model alias resolves to any advisory-flagged model. See LLM Providers — Model advisories.

Grok Build uses grok --prompt-file /dev/stdin --output-format streaming-messages-json --permission-mode bypassPermissions --model <model> --cwd <cwd> --session-id <uuid> --no-plan --no-ask-user --no-auto-update --no-leader and expects users to authenticate with grok login or XAI_API_KEY. grok is a generic executable name shared with a stale community CLI (grok-dev) and Homebrew's deprecated regex tool. Like Muse, Grok never participates in default-provider autodetection. It is reached by explicit selection (see llm_provider.provider above) or by the separate model-alias router: whichever shipped size aliases currently target Grok can select it whenever a grok executable is available (see the generated shipped size-alias defaults). Routing checks executable presence, while sase doctor performs the Grok Build identity probe. Grok models accept only low/medium/high/xhigh for --effort; see LLM Providers — Reasoning Effort.

VCS Provider

Variable Description
SASE_VCS_PROVIDER Override VCS provider selection (git, hg, or auto).
SASE_WORKSPACE_ROOT Override the workspace-root base for this process. Use an absolute path; WorkspaceStore appends <project_key>/<project>_<num>/ for managed checkouts.
SASE_BUG_ID Bug ID for PR workflows. When set and non-zero, injects SASE_BUG=<id> into PR tags and Patch.
SASE_BEAD_ID Assigned bead ID for commit workflows. When set, sase stitch create adds a linked SASE_BEAD= footer tag, leaves the subject unchanged, and requires an explicit -B/--bead-action keep\|close (see Explicit Bead Action).
SASE_GH_TIMEOUT Positive per-attempt timeout, in seconds, for SASE's retried gh CLI calls (default: 20).
SASE_GH_MAX_RETRY_SLEEP Positive cap, in seconds, on one wait between gh retries, including a server-requested retry delay (default: 60).
SASE_LINKED_REPOS_JSON Resolved linked-repo metadata passed to launched agents.
SASE_LINKED_REPO_<ENV_NAME>_DIR Workspace-matched directory for one configured linked repo.

SDD Git Operations

Variable Description
SASE_SDD_STORE_WRITE_LOCK_TIMEOUT Non-negative seconds to wait for the cooperative SDD store lock. Overrides both the 10-second metadata-write default and 180-second worktree-mutation default.
SASE_SDD_GIT_LOCK_RETRY_DELAYS Comma-separated non-negative delays, in seconds, for transient Git lock failures. Invalid or empty values use the built-in shared retry schedule.
SASE_SDD_GIT_LOCAL_TIMEOUT Positive seconds allowed for one local SDD Git command (default: 30).
SASE_SDD_GIT_NETWORK_TIMEOUT Positive seconds allowed for one network SDD Git attempt (default: 120). For progress-reporting clone, fetch, and push, it is the no-progress stall limit.
SASE_SDD_GIT_NETWORK_TRANSFER_CEILING Positive wall-clock cap, in seconds, on one progress-reporting clone, fetch, or push even while it keeps making progress (default: 900).
SASE_SDD_GIT_SLOW_MS Positive duration, in milliseconds, at which a successful SDD Git command is logged as slow (default: 1000).
SASE_SDD_REMOTE_CLONE_CONCURRENCY Positive number of SDD sidecar remote clones allowed to run at once on this machine (default: 1).
SASE_EPIC_PLAN_LAUNCH_LOCK_TIMEOUT Positive seconds an epic plan launch waits for another launch in the same project (default: 900).
SASE_EPIC_APPROVAL_PREFLIGHT_LOCK_TIMEOUT Positive seconds an approval preflight waits for an in-flight epic launch before deferring its health check to the detached launch (default: 120).

See SDD storage concurrency and recovery for the lock, recovery snapshot, and failed-integration cooldown behavior.

Plugin System

These switches affect plugin-provided resources and declarative artifact providers. The VCS, workspace, and LLM registries load provider entry points directly.

Variable Description
SASE_DISABLE_PLUGINS Disable plugin resources and third-party artifact-provider entry points.
SASE_DISABLE_PLUGIN_MACROS Disable plugin-provided macro and workflow files.
SASE_DISABLE_PLUGIN_CONFIG Disable plugin-provided default_config.yml files and config macros.
SASE_DISABLE_PLUGIN_ARTIFACT_REFS Disable plugin-provided artifact-reference specifications.
SASE_DISABLE_PLUGIN_FILE_HOOKS Disable plugin-provided file-hook templates.
SASE_DISABLE_PLUGIN_TASK_TYPES Disable plugin-provided task-type specifications.

State Root

Variable Description
SASE_HOME Override the SASE state root. Defaults to ~/.sase; project files, chats, artifacts, notifications, dismissed bundles, saved groups, and logs move under this root.
SASE_PROC_LOG_MAX_BYTES Maximum active proc-log segment size in bytes (default: 2 MiB); 0 disables rotation.
SASE_CUTOVER_BACKUP_DIR Root the local state cutover kit writes backups, restores, and run journals under. Defaults to ~/cutover-backups; it must not be nested beneath ~/.sase, ~/.local/state/sase, or ~/sase, and users of other runtime roots must keep it separate themselves.

General

Variable Description
SASE_TMPDIR Override SASE's managed temp root. When unset, the root is $SASE_HOME/tmp (~/.sase/tmp by default). Keep it off tmpfs and out of file-sync folders, because build scratch can be large.
SASE_DETACH_SCOPE_DISABLE Set to 1, true, yes, or on to stop detached agent runners, procs, monitors, and scheduler/hook/bead-sync workers from escaping SASE's systemd cgroup. By default on Linux, when the launcher runs inside a SASE-owned systemd unit or scope, or anywhere under a reachable user systemd manager, and systemd-run exists, SASE starts that work in its own transient systemd-run --user --scope with OOMPolicy=continue.
SASE_AXE_DISABLE_SYSTEMD_SCOPE Legacy alias for SASE_DETACH_SCOPE_DISABLE: set to a truthy value to stop the same detached work from escaping SASE's systemd cgroup.
SASE_AGENT_AUTO_APPROVE Launch-time auto-approve snapshot exported only while autonomy_record_only is off for a %auto agent; nothing in SASE reads it. SASE never consults it when deciding a gate: only the live agent_meta.json counts, so the A toggle takes effect at the next gate without updating this variable.
SASE_FEATURE_FLAGS Strict JSON object of booleans carrying the resolved feature-flag snapshot for this process and its children. Outranks config layers and a saved machine preference. Root -f/--enable-feature and -F/--disable-feature merge into this variable so launched processes inherit those CLI overrides.
SASE_MACRO_LSP_CMD Override the command used by sase lsp to launch the macro language server.
SASE_CORE_DIR Preferred sase-core source checkout for Justfile Rust build/install targets; overrides ../sase-core.
SASE_RUST_DEV_PROFILE Cargo profile for the just Rust dev-install recipes and editable sase-core update prebuilds (default: dev-update); set release to force the published release profile. See Rust Backend.
SASE_FORMAT_VENV_DIR Virtualenv the just formatting recipes install their narrow formatter tool set into (default: .venv-format).
SASE_PYTEST_DIST xdist scheduler for the just pytest recipes: worksteal (default) or loadfile (fallback). Invalid values fail before worker-token acquisition; serial inline-snapshot modes ignore it.
SASE_PYTEST_SANDBOX_DIR Pytest-published sandbox root inherited by test subprocesses; bead-store writes during pytest must target a path at or below this directory.
SASE_PYTEST_WORKERS Request exactly this positive number of governed xdist workers for the just pytest recipes. The request must fit the active host pool unless accounting is deliberately disabled.
SASE_PYTEST_WORKER_FLOOR Positive minimum token grant required to start an automatically sized just pytest run. Defaults to 4, clamped on smaller hosts, and cannot exceed the ceiling or host pool.
SASE_PYTEST_WORKER_CEILING Positive maximum token grant for an automatically sized just pytest run. Defaults to at most 28 while reserving another floor-sized grant when capacity permits.
SASE_ALLOW_UNSANDBOXED_BEAD_WRITES Test-only override; set to 1 to allow a pytest bead-store write outside SASE_PYTEST_SANDBOX_DIR for a deliberate exception.
SASE_ACE_PAGE_GROUP_ISOLATION Test-only override; set to 1 to make AcePageGroup create a fresh AcePage for each checkout instead of sharing one app across related tests. just test-ace-page-group-isolated sets this for every module in tests/ace/tui/ace_page_group_files.txt.
SASE_TEST_GATE_SLOTS Override the host-wide pytest capacity in worker tokens. Unlike the former whole-suite gate, one token now represents one xdist worker.
SASE_TEST_GATE_DIR Override the shared pytest token-pool directory. Defaults to a UID-scoped sase-pytest-tokens-<uid> directory under /tmp.
SASE_TEST_GATE_TIMEOUT Non-negative seconds to wait for a sufficient worker-token grant before failing with requested capacity and current-holder diagnostics.
SASE_TEST_GATE_STALE Non-negative seconds without a progress heartbeat before a live holder is treated as wedged and reclaimed. Default 1800 (30 minutes). 0 disables stale-heartbeat reclaim.
SASE_TEST_GATE_MAX_HOLD Non-negative seconds a live holder may keep its grant even while heartbeats continue. Default 14400 (4 hours). 0 disables the absolute age cap.
SASE_TEST_GATE_WATCHDOG Non-negative seconds between a holder's self-checks of those bounds. Default 30. 0 disables the holder-side watchdog; waiters still reclaim.
SASE_TEST_GATE_DISABLED Set to 1 to bypass the pytest worker-token pool deliberately. The bypass takes no tokens and never waits, but its width is still clamped to the host budget and announced on stderr; raise SASE_TEST_GATE_SLOTS to run wider. Every held lease also exports it to prevent nested pytest deadlocks.
SASE_TEST_GATE_GOVERNED Internal marker exported by every held worker-token lease, meaning an ancestor already paid for this process's workers. It is what separates a corroborated exemption from a top-level bypass; inherited pytest configuration must not lease again.
SASE_TEST_SHARD <1-based index>/<1-based count> (e.g. 3/8). Runs exactly one deterministic, balanced slice of the whole fast suite instead of all of it; just pytest recipes only accept this in fast mode and reject an explicit test selector alongside it. Set by .github/workflows/master-gate.yml, not intended for manual use.
SASE_JUST_INVOCATION_DIR Internal value set by just so test selectors are normalized from the caller's directory.

The pytest variables above describe one UID-scoped pool shared by just recipes and direct parallel pytest controllers. The first active lease records the effective capacity; later launchers honor that capacity until every holder exits, even if MemAvailable changes in the meantime. Automatic launchers require their floor atomically and then take currently free tokens up to the ceiling. Exact SASE_PYTEST_WORKERS requests wait for the complete request, and an explicit SASE_TEST_GATE_SLOTS value must match an already-active pool. The former whole-suite slot gate is fully superseded: admission, diagnostics, and SIGKILL-safe release are all expressed in worker tokens. A live holder also writes a progress heartbeat (collection and completed test calls). Waiters and a holder-side watchdog reclaim a grant whose heartbeat is older than SASE_TEST_GATE_STALE or whose age exceeds SASE_TEST_GATE_MAX_HOLD: the watchdog releases its own tokens, and a waiter SIGTERMs (then SIGKILLs) a still-held wedged process so flock can return the tokens to the pool. Waiting and timeout messages print each holder's age, heartbeat age, and reclaim reason.

Workspace Management (Internal)

These are set automatically by sase when launching agent subprocesses and are not intended for manual use. Workspace plugins declare an env-var prefix, then SASE passes <PREFIX>_PRE_ALLOCATED, <PREFIX>_WORKSPACE_NUM, and <PREFIX>_WORKSPACE_DIR into the child process. Built-in prefixes include SASE_GIT for #git; plugin packages may add prefixes such as SASE_GH for GitHub. The launcher clears inherited SASE_*_PRE_ALLOCATED, SASE_*_WORKSPACE_NUM, and SASE_*_WORKSPACE_DIR variables before applying the current launch's values so follow-up agents cannot inherit stale workspace claims.

Variable Description
SASE_SYNC_CWD Working directory override for sync operations.
<PREFIX>_PRE_ALLOCATED Set to "1" when a workspace provider has pre-allocated a launch context.
<PREFIX>_WORKSPACE_NUM Pre-allocated workspace number.
<PREFIX>_WORKSPACE_DIR Pre-allocated workspace directory path.
SASE_GIT_*, ... Concrete forms for built-in and plugin-provided workspace prefixes.

CLI Flags

Command groups that default to a nested list command still parse flags at the subcommand level. Use the explicit list form when passing list options, such as sase notify list -j, sase agent hold list -j, or sase workspace list --json. See the CLI Reference for which groups delegate to list.

sase (global)

These options are recognized only in the leading run of option tokens, before the first subcommand. They do not steal -f/-F or -p from commands such as sase bead list -f json or sase completion candidates -p PROJECT.

Flag Values Default Description
-F, --disable-feature registered flag key - Force a registered feature flag off for this invocation and every process it launches. Repeatable. Outranks config layers, a saved machine preference, and an inherited SASE_FEATURE_FLAGS value.
-f, --enable-feature registered flag key - Force a registered feature flag on for this invocation and every process it launches. Repeatable. Outranks config layers, a saved machine preference, and an inherited SASE_FEATURE_FLAGS value.
-p, --print-command flag - Print a shell-quoted sase ... header, prefixed with ❯ (or > when stderr cannot encode it), to stderr before running the command. The header keeps feature-flag overrides but omits the root print switch, and it is never printed during tab completion.

sase tui

Flag Values Default Description
[query] string last used, first saved, or !!! Query string for filtering Patches.
-m, --model-tier large, small - Override model tier for all LLM invocations.
-M, --model-size big, little - Deprecated alias for --model-tier.
-p, --profile optional path - Profile the TUI session with pyinstrument. Without a path, write ace-profiles/ace_profile_<timestamp>.txt under SASE's managed temp root; after exit, print a shortened path and copy it to the system clipboard when possible.
-r, --refresh-interval int (seconds) 10 Auto-refresh interval (0 to disable).
-R, --restart-service, --restart-axe flag - Restart the service host on startup; no-op if it is not running.
-s, --sanity-refresh-interval int (seconds) 300 Full sanity-refresh interval; missed watcher or change-token updates are still reconciled at least this often.
-t, --tab artifacts, changespecs, patches, agents, services, axe agents Tab to focus on startup. services and legacy axe select Services; changespecs and patches select Artifacts.
-T, --tmux flag - Launch sase's TUI in a new tmux window named sase_tmux_<N> and print the session/window target for external control.
-x, --no-service, --no-axe flag - Disable auto-starting the service host on startup.
-v, --vcs-provider git, hg, auto - Override VCS provider.

sase screenshot

Capture a PNG from a real sase tui running in tmux. The command launches in the detached sase_ace_agents session unless --window names an existing tmux target. See Agent screenshots.

Flag Values Default Description
-- TUI_ARGS strings - Arguments forwarded to sase tui after a -- separator.
-d, --settle-ms int (ms) 0 Extra delay before capture.
-H, --host alias / SSH local machine Run the SVG capture on a remote machine, then rasterize locally.
-k, --keep flag - Leave a newly launched tmux window running after capture.
-o, --output path managed temp path PNG output path, or SVG output path when --svg is used.
-p, --press tmux key - Send one tmux key. Repeats interleave with --type and -w in argv order.
-s, --size COLSxROWS 120x40 Geometry for a newly launched capture window.
-S, --svg flag - Stop after live-app SVG export and skip PNG rasterization.
-t, --timeout seconds 60 Overall capture deadline.
-T, --type text - Send literal TUI text (tmux send-keys -l). Repeats interleave with -p and -w in argv order.
-w, --wait-for regex - Wait for captured tmux screen text to match a regex. Repeats interleave with -p and --type in argv order.
-W, --window tmux target launch a new window Capture an existing sase_tmux_* window and never kill it after capture.

sase tmux-agent

Launch an interactive agent CLI in a new tmux window. There are no subcommands — in particular no list child — so a bare sase tmux-agent paints the tmux menu instead of delegating. See tmux Agent.

Flag Values Default Description
[provider] registered provider name paint the menu Launch this provider directly. Omit to paint the tmux Agent menu.
-c, --dir path current pane path, else $PWD Launch directory.
-e, --effort off, none, minimal, low, medium, high, xhigh, max inherit from config Explicit effort for this launch; unsupported levels are a usage error.
-j, --json flag - Versioned JSON envelope of the catalog or the dry-run plan.
-l, --list flag - Print the catalog as a table; works outside tmux. Not a subcommand.
-n, --dry-run flag - Print the window name, directory, env, and exact command; change nothing.
-r, --refresh flag - Rebuild the catalog cache before doing anything else.
-s, --safe flag - Launch without the provider's approval-bypass args.
-v, --verbose flag - With --list, add resolved paths, full commands, and install hints.

--renumber is an internal hook invoked when an agent CLI window exits and is omitted from help. Outside tmux with no --list/--dry-run/--json, the command exits 2, explains that a tmux session is required, and still prints the catalog.

sase service

Bare sase service defaults to status, and bare sase service proc defaults to proc list.

Command Flags / arguments Description
sase service status -j, --json Show host/native-unit state and every configured proc; exits 0 for running/starting, otherwise 1.
sase service start -j, --json Start through the installed native unit when present, otherwise start a detached host.
sase service stop -j, --json Stop the host.
sase service restart -j, --json Stop and start the host.
sase service run - Run the host in the foreground until SIGINT/SIGTERM.
sase service logs -n, --lines N Print the bounded host log (default 200 lines).
sase service init -a/--allow-agent-env, -c/--check, -d/--diff, -f/--force, -y/--yes Plan, check, diff, or install/update the native user unit and captured environment; --yes refuses a SASE agent process unless -a is given.
sase service uninstall -c/--check, -d/--diff, -f/--force, -y/--yes Plan, check, diff, or remove the native user unit.
sase service proc list -j, --json List effective enablement, desired state, runtime state, and summary.
sase service proc show NAME -j, --json Show source, launcher, effective enablement, state, and log path.
sase service proc logs NAME -n, --lines N Print one proc's bounded output log (default 200 lines).
sase service proc start NAME -n/--no-wait, -t/--timeout SECONDS Record a start request the host confirms; prints the pid.
sase service proc stop NAME - Stop the proc until the next host boot.
sase service proc restart NAME -n/--no-wait, -t/--timeout SECONDS Record a restart request the host confirms; prints pid OLD -> pid NEW.
sase service proc enable/disable NAME - Persist a machine-local enabled or disabled override.
sase service proc run -- COMMAND... -c/--cwd, -j/--json, -l/--label, -p/--project, -w/--workspace Submit a transient durable oneshot that is never added to daemon desired state.

Without --yes, init and uninstall only print the plan and the apply command. Their --check forms are read-only and return 1 for drift. --force allows a non-default SASE_HOME and uses a home-scoped unit identity; uninstalling that unit likewise requires --force. Platform lifecycle installation supports Linux systemd user units and macOS LaunchAgents. See service configuration for entry fields and layering.

sase axe

sase axe start|stop|restart|status is an alias of the matching sase scheduler command. -v/--vcs-provider below is a sase axe group flag only; it applies to every axe subcommand and has no scheduler spelling.

Flag Values Default Description
-v, --vcs-provider git, hg, auto - Override VCS provider.

sase axe status

Shows the scheduler service proc's status through the service host. Human output is the default; JSON output is deterministic and never contains Rich markup or ANSI escapes. -j, --json emits the proc record. See Scheduler Status.

Flag Values Default Description
-j, --json flag - Emit the proc record as JSON.

sase axe start

Asks the service host to start the scheduler service proc and waits for the host to confirm. -n/--no-wait returns once the request is recorded; -t/--timeout SECONDS bounds the wait. The -q, -H, -A, and -z overrides live on sase scheduler run (and sase axe routine run), not on start.

sase axe stop

Asks the service host to stop the scheduler service proc and returns once the request is recorded. It takes no flags.

sase axe restart

Asks the service host to stop and start the scheduler service proc and waits for the host to confirm the new pid. -n/--no-wait returns once the request is recorded; -t/--timeout SECONDS bounds the wait.

sase axe maintenance

Maintenance mode pauses scheduled routine ticks without stopping the orchestrator.

Command Flags / exit code Description
sase axe maintenance enter -r, --reason required Write the maintenance marker with a reason.
sase axe maintenance exit exits 0 Remove the marker if present.
sase axe maintenance status exits 0 when active, 1 when inactive Print the active marker reason, PID, timestamp.

See axe.md — Maintenance Mode for the runtime behavior.

sase axe job

With no subcommand, sase axe job defaults to sase axe job list. Use the explicit list or doctor subcommand when passing diagnostic flags.

Form Flags Description
sase axe job list -a/--available, -j/--json, -v/--verbose List configured jobs with one summary line each; --available also shows discoverable executable job scripts, and --verbose adds a full-description panel.
sase axe job doctor -j/--json, -v/--verbose Diagnose missing configured jobs, unconfigured scripts, and Telegram job prerequisites.
sase axe job run <JOB> -L/--routine, -n/--dry-run, -f/--force, -V/--job-verbose Run a single job once in the foreground. -L names the routine when the job appears in several, -n runs the script but only previews its agent proposals, -f bypasses declarative guards (triggers are always bypassed), and -V enables verbose script diagnostics and the full structured result.

sase axe job doctor exits 1 when any check is ERROR (a configured script job cannot be resolved) and 0 otherwise. Unconfigured available scripts and Telegram prerequisite gaps report WARN. The same job diagnostics are also surfaced by sase doctor -C axe.jobs. The hidden legacy sase axe chop ... group accepts the same subcommands (plus the hidden --chop-verbose and --lumberjack spellings) and keeps its schema-version-1 JSON; see Compatibility aliases.

sase axe routine

With no subcommand, sase axe routine defaults to sase axe routine list.

Form Flags Description
sase axe routine list -v/--verbose List configured routines and their jobs; --verbose adds each description body under a details block.
sase axe routine run -q, -H, -A, -z Run one routine once in the foreground with optional query and runner-limit overrides.
sase axe routine status - Show per-routine process status.

Both listings print only the description summary line by default so the output stays scannable; -v/--verbose renders the full description. sase axe routine run takes the routine name positionally; its runner flags are the ones the orchestrator forwards when it spawns routines. The hidden legacy sase axe lumberjack ... group accepts the same subcommands.

sase stitch create

Dispatches a commit, proposal, or PR via the VCS provider layer. See commit_workflows.md for the full flow, payload, checkpoint, and resume semantics.

Flag Values Default Description
-m, --message string - Commit message (mutually exclusive with -M).
-M, --message-file path - File containing the commit message / PR description (mutually exclusive with -m).
-x, --exclude path (repeatable) - Repo-relative file or directory to leave out of the commit.
--only-file path (repeatable, hidden) stage all Internal: restrict the commit to these repo-relative paths. Mutually exclusive with -x.
-n, --name string - Branch/PR name (required for create_pull_request).
-b, --bug-id int $SASE_BUG_ID Bug ID to associate with the commit.
-B, --bead-action keep / close - Explicit assigned-bead action; required when committing with an assigned bead.
-c, --checkout-target string HEAD~1 Branch point for PR creation.
-p, --parent Patch name auto Parent Patch name (overrides branch-based auto-detection). Unresolvable values are dropped.
-r, --resume flag - Resume a previously-checkpointed commit after manual conflict resolution.
-s, --status wip / draft / ready ignored Accepted by the parser but currently not forwarded to the commit workflow, so it has no effect. Use $SASE_PR_STATUS instead.
-t, --type commit / propose / pr … $SASE_COMMIT_METHOD Commit method — full names (create_commit, etc.) and short aliases are both accepted.

sase stitch

sase stitch defaults to sase stitch list, which shows a merged timeline for the primary repo and configured linked repos. Add -S/--sdd to include sidecar repository history. The legacy sase vcs spelling is still accepted as a deprecated alias.

Subcommand Flags Description
list -a/--all, -A/--author, -b/--branch/--ref, -c/--color, -o/--current-only, -F/--fetch, -f/--format pretty\|full\|oneline\|json, -n/--limit, -m/--merges hide\|show\|only, -N/--no-fetch, -T/--no-tags, --origin stitch\|auto\|manual, -r/--repo, -R/--reverse, -S/--sdd, -s/--since/--after, -u/--until/--before Show a merged commit timeline with local/remote presence markers.

sase stitch list date filters accept relative offsets (Nh, Nd, Nw), today, yesterday, YYYY-MM-DD, or YYYY-MM-DDTHH:MM. Day-granular --until / --before values include the full named day; relative and minute-precise values remain instant bounds. See VCS Providers for output examples and provider notes. --all spans every registered enabled or disabled project and deduplicates shared physical checkouts. Internal sibling backing checkouts remain visible as linked repositories of their owning projects. Global scope can be combined with repeatable --repo filters but not --current-only. Add --sdd to either scope before selecting SDD history with --repo sdd; without the opt-in, that repo filter does not expand the eligible set. --all --sdd includes materialized separate SDD repositories across registered projects. The --limit is the cap on the final merged timeline (default 40; 0 means unlimited). --origin has no short form; repeat it to OR stitch, auto, and manual origins.

Flag Values Default Description
query string (required) Query string for filtering Patches.
-f, --format plain, rich, markdown rich Output format (markdown for agent-friendly output).

Search uses the normal enabled-project discovery scope. Disabled projects and internal sibling backing records are omitted from this CLI path; run sase project list --state all or sase project show <project> to inspect them, then run sase project enable <project> before using normal search and launch surfaces for new work.

sase patch migrate-extension

One-time cleanup for older installs: renames legacy ProjectSpec files under ~/.sase/projects from .gp to .sase, including archive siblings. Current readers still accept .gp as a fallback, so migration is not required before using SASE; it just normalizes on-disk filenames to the canonical extension.

If a .sase sibling already exists with identical contents, the redundant .gp copy is removed. If the sibling differs, the command reports a conflict and preserves both files unless --force is set.

Flag Values Default Description
--force flag - Replace an existing differing .sase sibling with the legacy .gp file.
--projects-dir path ~/.sase/projects/ Override the project root scanned for legacy .gp files.

sase project

With no subcommand, sase project defaults to sase project list. Project lifecycle state is stored as PROJECT_STATE metadata in the ProjectSpec header; missing state means enabled.

Form Flags Description
sase project list -s, --state enabled\|disabled\|sibling\|all List records in one state; default is true enabled projects.
sase project list -j, --json Emit machine-readable lifecycle and derived project/VCS fields.
sase project current -j, --json Show the current project derived from the VCS macro MRU.
sase project set-current <project> -j, --json Promote a project to the VCS macro MRU head without launching an agent.
sase project show <project> -j, --json Show state, source, project/archive files, workspace, launchability, warnings.
sase project set-state <project> <state> -f, --force Set enabled, disabled, or internal backing marker sibling.
sase project enable <project> -f, --force Enable a project; --force has no effect when enabling.
sase project disable <project> -f, --force Disable a project after live-work safety checks.

sase project set-current promotes an enabled project to the VCS macro MRU head without launching an agent. That is display and filter-seed context, not a lifecycle state; see Current project.

Disabling refuses projects with live RUNNING claims or live artifact markers (running.json, waiting.json, or pending_question.json) unless --force is passed. Legacy active normalizes to enabled; inactive, archived, and closed normalize to disabled. Deprecated activate, deactivate, archive, and close command aliases remain accepted. The system-managed home project cannot be mutated. Normal launch and discovery surfaces default to enabled projects. sibling remains an internal backing-record marker for configured linked repos, not a third project state.

sase's TUI exposes the same lifecycle mutations through the Projects tab of the SASE Admin Center (press #). That tab also supports marks for bulk lifecycle operations, alias editing with A, ProjectSpec editing through $EDITOR, confirmed deletion of whole SASE project directories, and the Repos/Workspaces inventory sub-tabs described above.

sase repo

Bare sase repo defaults to sase repo list. The command family inventories primary, sidecar, linked, and opened external repositories, prepares a selected repo inside one workspace context, and exposes the durable audit history of successful opens.

sase repo list defaults to the current project and infers both the project and workspace context from cwd. Primary repos come from ProjectSpecs, sidecars from repos.sidecar plus SDD store records, linked repos from resolved repos.linked (including compatibility aliases), and external repos from materialized workspace-local clones; a sidecar wins when the same checkout is also auto-injected as linked. The Rich table reports whether each repo is cloned in the selected workspace plus the number of registered workspaces containing it. External rows use the canonical project name or provider ref such as gh:pallets/click. Hidden agents rows remain visible and report the same stable machine-level path for every registered workspace context.

List flag Description
-a, --all Show all enabled and disabled projects at primary workspace context (#0).
-j, --json Emit deterministic records with the full per-workspace clones matrix.
-p, --project Select one enabled or disabled project instead of inferring from cwd.
-w, --workspace Select a workspace number instead of inferring it from cwd.

--all and --project are mutually exclusive. JSON records retain source, description, auto_clone, environment, and SDD-storage metadata while making path and exists describe the selected workspace context.

sase repo open REPO -r "<reason>" resolves REPO in three tiers: a host-project inventory name, another registered SASE project name, then an external provider ref (gh:owner/repo or owner/repo GitHub shorthand). It materializes and prepares the repo, prints only its path to stdout, records the per-run artifact markers used by sase's TUI and the commit finalizer, and appends an event to ~/.sase/projects/<project>/repo_opens.jsonl. Run it inside a managed checkout to infer the host project and workspace. Reopening a valid external clone preserves its current contents and records a new open event.

When a supported provider ref or another registered SASE project corresponds to a configured linked repo in the host project, sase repo open opens the linked checkout instead of cloning or reopening an external copy. Stdout remains exactly the linked path; stderr explains the redirect and suggests the configured linked name for next time. If a previous external checkout of the same repo exists, it is left untouched and reported as a warning so any work already there remains visible.

Open argument / flag Description
REPO Inventory name, registered project name, gh:owner/repo, owner/repo, or record path.
-p, --project Select the host project instead of inferring it from cwd.
-r, --reason Required non-empty audit reason.
-w, --workspace Select the host workspace number instead of inferring it from cwd.

When two inventory records share a name or slug, sase repo open refuses rather than guessing and lists each candidate as <kind> '<name>' (<path>). To pick one, re-run the command with that candidate's path as REPO, copied exactly as printed: a record path is matched literally, ahead of any name or slug, so it selects that record even when its name collides with another's. This is a disambiguator, not a general "open any directory" mode — a path that matches no primary, sidecar, or linked record falls through to the usual name and provider-ref tiers and fails there.

sase repo log renders a project-scoped summary and per-repo rollup of durable open events. Repo, agent, or workspace filters add agent and event drill-down panels; an event ID prefix shows one complete event. --json returns the same filtered data deterministically.

Log flag Description
-a, --agent Filter by agent name or interactive user.
-i, --id Show one event by exact ID or unambiguous ID prefix.
-j, --json Emit deterministic structured output.
-p, --project Select the host project instead of inferring it from cwd.
-r, --repo Filter by repository name.
-w, --workspace Filter by host workspace number.

sase revert

Flag Values Default Description
name string (required) NAME of the Patch to revert.

sase restore

Flag Values Default Description
[name] string - NAME of the reverted Patch to restore.
-l, --list flag - List all reverted Patches.

If restoration reaches commit recreation, the current implementation calls sase stitch create with an unsupported positional Patch name and fails after applying the saved diff. See VCS restore for the resulting workspace state and manual recovery command.

sase run

Flag Values Default Description
[PROMPT] string - Prompt text, inline reference (#name), standalone workflow reference (#!name), or . for history picker.
-Q, --request-path path $SASE_PROC_REQUEST_PATH Private operation request sidecar used when sase's TUI runs the launch as a durable proc.
-R, --result-path path $SASE_PROC_RESULT_PATH Private typed result sidecar for the same durable-proc path.

When invoked with no arguments, opens $EDITOR for composing a prompt interactively. When invoked with ., opens a prompt history picker. All prompts launch as detached background agents, and multi-prompt queries (containing --- separators) are launched as sequential detached background agents.

From an interactive terminal outside an agent or durable proc, a prompt whose %hold preview combines future with scope=host prints the preview and asks Arm this hold? [y/N]. An over-threshold pending capture also asks when the preview can resolve project context; typed launch plans can do so, while a plain project-scoped sase run prompt currently cannot and skips this confirmation. Declining prints Hold not armed; launch cancelled. and exits 1. Despite that prompt text, accepting only permits the launch during the current beta; it does not arm the hold.

sase repro

sase repro captures and replays debugging bundles for narrow, reproducible TUI bug classes. The current target is the Agents-tab loader/apply sequence used to diagnose row disappearance, reappearance, and duplicate workflow parents; see Agents Tab Reproduction Bundles. All of its options are long-only.

Form Flag Values Default Description
sase repro capture agents-tab --output path required Directory where agents_tab_repro.json and capture artifacts are written.
sase repro capture agents-tab --commit-safe flag enabled Redact local names and paths for a shareable bundle.
sase repro capture agents-tab --no-commit-safe flag - Keep unredacted local identifiers in the capture.
sase repro capture agents-tab --size WxH 120x40 Terminal size label stored with the bundle.
sase repro capture agents-tab --json flag - Emit a machine-readable capture result.
sase repro replay path path required Bundle JSON file or bundle directory to replay through the headless TUI.
sase repro replay --assert-stable flag - Exit non-zero if replay invariants fail.
sase repro replay --json flag - Emit a machine-readable replay verdict.
sase repro replay --write-artifacts path - Directory for replay screen text and SVG artifacts.
sase repro replay --size WxH 120x40 Headless terminal size used for replay.

Use the in-TUI ,B capture when a transient row-list bug has just happened in a live sase's TUI session. The CLI capture path is out-of-band: it loads current filesystem state and cannot reconstruct refreshes that already passed through the running TUI.

sase macro

With no subcommand, sase macro defaults to sase macro list.

sase macro expand

Flag Values Default Description
[prompt] string stdin Prompt text to expand (reads from stdin if omitted).
-t, --trace flag - Print expansion trace to stderr showing resolved references.

sase macro explain

Flag Values Default Description
workflow_name string (required) Workflow name to explain.
[args] string - Positional arguments for the workflow.
-a, --arg string - Named argument as KEY=VALUE (repeatable).

sase macro list

No flags. Outputs a JSON array of all available macros with name, type, source, inputs, tags, is_skill, and preview. Clients that insert references should prefer kind/insertion metadata when present so standalone workflows are inserted as #!name and inline-capable entries, including markdown macro swarms, are inserted as #name. Slash skill completion clients should filter to entries where is_skill is true.

sase macro show

Flag Values Default Description
NAME string (required) Macro or workflow name; copied markers and argument suffixes are tolerated.
-c, --color auto,always,never auto Color mode for rendered output.
-f, --format full,json,raw full Rendered detail view, stable JSON record, or exact definition source bytes.
-p, --project string auto Resolve within a specific project namespace instead of the detected project.

sase macro graph

Flag Values Default Description
[workflow_name] string - Workflow name to graph. Lists all workflows if omitted.
-f, --format mermaid,text mermaid Output format for the DAG visualization.

sase macro catalog

Flag Values Default Description
-o, --out path tempdir Directory where the rendered PDF should be saved.

sase macro types

Flag Values Default Description
[NAME] string - Bare type (effort), alias, builtin@<name>, or <dist>@<id>.
-j, --json flag - Print the Rust catalog projection as JSON.

With no NAME, prints grouped tables of scalar, builtin, and plugin types. With NAME, shows one type's detail card. Unknown names exit 1.

sase init

Bare sase init is the onboarding coordinator for SASE-managed resources. It runs read-only planners for config (owner identity), machine enrollment, memory, repositories, and skills, in that order, prints a grouped summary, and prompts once per initializer that needs work when stdin is interactive. Non-interactive runs never prompt; they print the drift summary and ask the caller to rerun with --yes. That flag runs needed initializers but cannot authorize creation of a missing provider sidecar repository, which always requires its own interactive y/yes response. The memory planner (which owns agent-document initialization) only generates managed project AGENTS.md from bare sase init when the current project's own sase/sase.yml sets is_sase_managed: true. The repository planner uses that same local marker and skips unmanaged repositories before provider work. Neither planner infers project ownership from memory.h1_title, existing memory notes, lifecycle state, or merged configuration. See Initialization for the full flow.

--all applies that coordinator to every registered enabled main project from its recorded primary workspace, even when the command starts outside a project. It excludes disabled projects, internal sibling backing records, home, and other system-managed records, continues after per-project failures, and returns non-zero if any project has drift, is unavailable, or fails. --all --check is read-only, while non-interactive apply still requires --yes. --all is incompatible with --enable-project-memory and with explicit compatibility subcommands. -p/--project names a subset of the same enabled projects instead and has the same restrictions, plus it cannot be combined with --all.

Advanced deploy controls stay on explicit subcommands such as sase memory init --no-commit and sase skill init --no-push. The compatibility subcommands sase init config, sase init machine, sase init memory, sase init repo, and sase init skills accept the flags of sase config init, sase machine init, sase memory init, sase repo init, and sase skill init.

Flag Values Default Description
-a, --all flag - Attempt every known enabled main SASE project and report one aggregate status.
-c, --check flag - Report initialization drift without writing; exits non-zero when changes are needed.
-d, --diff flag - Show full file diffs for planned changes.
-j, --json flag - With --check, emit one schema-versioned JSON plan (current, drift, or blocked) instead of human output.
-M, --enable-project-memory flag - Mark the repository with is_sase_managed: true before initialization; cannot be combined with --check.
-p, --project project name - Check or initialize one enabled project by name, display name, or alias; repeatable.
-y, --yes flag - Run needed initializers without generic prompts; cannot approve creation of a missing provider sidecar repository.

sase machine

With no subcommand, sase machine defaults to the offline sase machine list. Bootstrap bundles contain live single-use secrets and are never accepted as command-line values. init always needs an interactive stdin for candidate and alias selection; it reads the bundle from -B/--bootstrap-file or a hidden prompt. add and repair also accept a bundle on piped stdin when --bootstrap-file is omitted.

Form Flags Description
sase machine / machine list -j/--json List configured aliases without provider or gateway I/O.
sase machine discover -j/--json, -p/--provider, -t/--timeout Query configured or repeatably selected discovery providers.
sase machine bootstrap -e/--expires, -j/--json, -s/--scope On the target, issue a scoped single-use bundle to stdout.
sase machine init -B/--bootstrap-file, -c/--check, -j, -t Interactively discover, enroll, reload, and verify; --check is offline.
sase machine add ALIAS [ENDPOINT] -B, -c/--candidate, -j, -p/--provider, -S/--ssh-target, -t Enroll a named HTTPS endpoint or discovered candidate, then activate.
sase machine show ALIAS -j/--json Show one local machine record, including its effective SSH target.
sase machine status [ALIAS ...] -j/--json, -t/--timeout Run authenticated hello checks; no aliases means all configured aliases.
sase machine repair ALIAS -B/--bootstrap-file, -j/--json, -t/--timeout Rotate a quarantined or mismatched enrollment and activate the replacement.
sase machine rename OLD NEW -j/--json Rename the viewer-local alias without changing gateway identity or credentials.
sase machine remove ALIAS -j/--json, -y/--yes Remove local config and its credential reference; interactive stdin prompts unless --yes.
sase machine agent {stop,retry,fork} action-specific arguments, -j, -t Submit a journaled remote lifecycle mutation; sase's TUI supplies revision-safe durable sidecars.
sase machine attention {answer,approve} action-specific arguments, -j, -t Answer a remote question or approve a gate through the same durable operation path.

See the Remote Dispatch Runbook for gateway supervision, Tailscale Serve, enrollment, launch constraints, and sase's TUI machine-row operation.

sase instructions

With no subcommand, sase instructions defaults to sase instructions list.

Form Flags Description
sase instructions - Show the same read-only agent-document inventory as sase instructions list.
sase instructions list - Inspect project, home, and chezmoi AGENTS.md files and provider shims.
sase instructions verify -a/--agent, -c/--coverage, -H/--helpers, -j/--json, -n/--limit, -p/--provider, -s/--since, -u/--until Show observed instruction loads per provider; -c adds manifest coverage, -a adds the section diff, -j emits schema_version: 1 JSON. Reports only, never gates.
sase instructions render -a/--agent, -f/--fact, -j/--json, -N/--no-cache, -p/--parity, -s/--sections Preview the memory-built bundle without delivering it; -j emits the preview manifest, -s the section table, -p checks legacy parity.

sase memory

With no subcommand, sase memory defaults to sase memory list.

Form Flags Description
sase memory - Show the same read-only memory context dashboard as sase memory list.
sase memory list - Show loaded, referenced, available, and missing memory files for the current launch context.
sase memory read <selector> ... -r, --reason <reason> required; -d/--depth, -f/--format, -p/--project Agent-side read of one or more selectors (a flat note, a bare web, or web:keyword) with one attributable audit event.
sase memory show <selector> ... -d/--depth, -f/--format, -p/--project Resolve and print the same selectors without recording a read.
sase memory log --path, --agent, --id, --include, --json Summarize or inspect audited memory reads; --include glossary folds in the legacy glossary log. These options are long-only.
sase memory web / web list -f/--format table\|names\|json, -p/--project List discovered memory webs with rendering, scope, and strand count.
sase memory web show <web> [PATTERN] -b/--bodies, -f/--format table\|names\|json, -p/--project Print one web's strand index (keyword, slug, aliases, reference count, summary), filtered by PATTERN; -b extends matching into strand bodies. Never prints bodies.

read and show resolve each selector from the project's sase/memory/ first and fall back to ~/sase/memory/. The whole batch is resolved before anything is printed or logged, so one unknown selector fails the request. -f/--format accepts markdown (the default), rich, or json. -d/--depth N caps link and mention closure recursion (unlimited by default); -d 0 prints only the requested selectors and lists every link as a reference. See Memory for attribution and log details.

Examples:

# read requires SASE agent identity
sase memory read generated_skills.md --reason "Need generated skill context"
sase memory read glossary:stitch glossary:patch -r "Need the terms"
sase memory show generated_skills.md -f rich
sase memory web show glossary agent
sase memory log
sase memory log --path generated_skills.md
sase memory log --id <read-id>

sase memory init

Creates or refreshes home memory and memory for SASE-managed projects. Project ownership requires is_sase_managed: true in the project's own sase/sase.yml; memory.h1_title is optional title customization, with a stable derived title otherwise. The retired memory.enabled key does not authorize management. It never creates or alters an unmanaged project's root AGENTS.md. Independently, it overwrites each provider instruction file (CLAUDE.md, GEMINI.md, QWEN.md, OPENCODE.md) with a byte-for-byte copy of that root's AGENTS.md (legacy @AGENTS.md / *.md.tmpl import shims are recognized and migrated to full copies). This copy applies to every existing project-tree AGENTS.md; directories without one are untouched. For managed roots, memory init synchronizes memory: core notes are inlined into the ## Core Memory block of AGENTS.md, every heading in the generated document is numbered, reference notes are rendered as numbered sections headed by the note path with the description as the body, and missing reference-memory description frontmatter is inserted. By default it also tries to commit, rebase-pull, and push generated project-side files. sase init memory is a compatibility alias for this command. Generated repository memory requires agents to use /sase_repo before reading or modifying any repo outside their own workspace checkout. The rule covers linked repos, sidecars, different SASE projects, and unlinked GitHub repos even when no linked repositories are configured. The same run also refreshes each memory web's inline strand roster — for example the glossary web's **GLOSSARY TERMS:** line in sase/memory/glossary.md — from its current strand files; sase memory init --check reports drift if a web's rendered roster or its Memory Webs inlining is stale. Unlike the generated notes above, sase/memory/glossary.md and every other web descriptor are user-owned content: memory init rewrites only the roster it renders, never the rest of the descriptor body.

Flag Values Default Description
-c, --check flag - Report memory initialization drift without writing project or home files.
-d, --diff flag - Show full file diffs for planned memory changes.
-M, --enable-project-memory flag - Set is_sase_managed: true, enabling managed project memory; incompatible with --check.
-m, --message string - Commit subject used when eligible memory source edits are folded into the generated-change commit; a docs(memory): tag is added if omitted.
-C, --no-commit flag - Write files, but skip only the project git commit/pull/push path; home deployment still follows config.

sase init repo

sase init repo is an alias for sase repo init. For targets marked is_sase_managed: true in their own sase/sase.yml, it initializes configured sidecars, creates or refreshes generated README files, ensures the managed plans and research declarations, and maintains the root /sase/repos/ ignore rule. Missing or false markers produce an informative successful no-op, while invalid local configuration fails before provider or filesystem work. The command always targets the Git repository that contains the current directory; it has no --path option. GitHub setup creates missing sidecars with their configured public/private visibility. Bare-git projects refresh generated files automatically during repository setup and first SDD writes; the explicit command remains useful for refreshes and --check audits.

When the GitHub sidecar is missing, this alias uses the same default-no repository-specific confirmation as sase repo init. EOF, interruption, and any answer other than y/yes return nonzero before remote creation. Generic --yes approval never authorizes repository creation; non-interactive bare onboarding instead reports the missing remote and defers its creation.

Flag Values Default Description
-c, --check flag - Report sidecar, config, and ignore-rule work without writing files.
-d, --diff flag - Show full file diffs for planned repository changes.
-C, --no-commit flag - Write project config and ignore rules without committing or pushing.

sase skill

With no subcommand, sase skill defaults to the read-only sase skill list dashboard. It reports loaded skill sources, provider targets, and deployed-file drift without writing files. sase skill init generates and deploys agent skill files from macro sources marked with the skill field. Generated skill files begin with a sase skill use directive so agent-side skill use can be audited and later summarized with sase skill log, unless the source sets log_skill_use: false. See macros.md — Skill Field for the skill-source contract and provider targets. Existing files are skipped in non-interactive runs unless --force is passed; interactive runs prompt before overwriting. Commit and land macro template changes before deploying: writing chezmoi deploys are refused from dirty or unmerged sources, and refused when they would move the destination off the source commit recorded in the provenance manifest — see Commit Before Deploying. sase init skills is a compatibility alias for sase skill init.

Form Flags Description
sase skill - Show the same read-only dashboard as sase skill list.
sase skill list - Inspect generated skill sources, provider targets, and deployed-file drift.
sase skill init -f, --force Overwrite deployed skill files without confirmation; bypass the provenance manifest guard.
sase skill init -D, --allow-dirty Deploy from uncommitted or unmerged macro sources; can revert other agents' deployments.
sase skill init -n, --dry-run Show what would be written without writing files.
sase skill init -c, --check; -d, --diff Report or diff generated skill-file drift without writing files.
sase skill init -p, --provider <name> Deploy only for one registered provider (claude, agy, codex, grok, muse, opencode, qwen).
sase skill init -y, --yes Answer the overwrite confirmation for existing generated targets; unlike --force, it does not override source-integrity or provenance-manifest guards.
sase skill init -A, --no-apply With use_chezmoi, skip chezmoi apply after generated files are committed and pushed.
sase skill init -C, --no-commit With use_chezmoi, skip the entire git commit, push, and apply sequence.
sase skill init -P, --no-push With use_chezmoi, commit generated files but skip pull/rebase, push, and chezmoi apply.
sase skill log -a, --agent; -R, --runtime; -s, --skill; -i, --id; -j, --json Summarize or inspect audited generated skill-use events.
sase skill use -r, --reason <reason> required Agent-side audit event recording that the current agent is using a generated skill.
sase init skills same as sase skill init Compatibility alias for sase skill init.

sase repo init

sase repo init declares the managed plans, beads, and default research document sidecar, initializes every enabled configured sidecar, and ensures the project root .gitignore contains /sase/repos/, protecting host-scoped repository clones durably. -c, --check reports drift without writing, -d, --diff renders proposed full-file diffs, and -C, --no-commit writes project config and ignore changes without the normal project commit/pull/push sequence. sase init repo is an alias; bare sase init and sase validate include the same check for Git projects.

sase tool

Named-tool catalog and foreground ToolRun commands. With no subcommand, sase tool defaults to sase tool list.

Command Flag / argument Values Description
sase tool list -j, --json flag Emit a versioned JSON object with LAST, TYPICAL, argv, and definition digest.
sase tool run TOOL [-- ARGS...] -q, --quiet; -T, --tail-lines; -v mixed Run a named tool at the project root. Extra args append only when the definition allows them.
sase tool run -H TOOL -H, --hand-off flag Hand the run off to a durable proc and return at once with the run id (-q prints only the id; -v/-T are usage errors). Exits 0 accepted, 1 not started, 2 usage or refusal.
sase tool run TOOL -k, --keep-going or -x, --fail-fast flag Continue past, or stop at, the first failed stage (named run_silent tools only).
sase tool run -- ARGV... same mixed Run an ad-hoc argv at the invocation cwd. The -- separator is required.
sase tool runs -a -A -c -j -n -s -t mixed List native ToolRuns (default: current project, limit 50, max 1000).
sase tool show RUN -j, --json or -l, --logs; -F flag Show one run by exact id; -F/--follow streams its output until it settles. -j and -l are mutually exclusive. Missing runs exit 2.
sase tool wait RUN -T, --tail-lines; -t; -j mixed Block until the run settles or the -t deadline passes (the run is never affected). Returns its exit code, 1 if settled without one, 124 on deadline, 130 on Ctrl-C.
sase tool stop RUN -j, --json flag Record a durable stop request and stop the run through its owner (proc, monitor, or inline wrapper). Exits 0 requested/stopped/settled, 2 unknown or refused, 1 owner failure.
sase tool failures -a -c -d -j -n -t mixed List grouped failure signatures from stored triage (default: current project, last 7 days, limit 50, max 1000).
sase tool receipt TOOL -a, --accept; -j, --json mixed Report the covering verdict receipt at the current fingerprint, or a typed refusal. Exits 0 covered, 1 refused, 2 usage. The query never claims a completion policy is met.
sase tool receipts -d, --days; -j, --json mixed List retained receipts and content-equivalent repeat opportunities over the last N days (default 7). Measurement only.
sase tool stats -a -d -j -t mixed Report ToolRun durations, waste, repeats, backtest, and pressure (default: current project, last 7 days; -d is 1..180). Read-only, machine-local. Exits 0 reported, 1 store failure, 2 usage.

Humans default to exact stdout/stderr passthrough; wrapper metadata goes to stderr. Direct agent execution (SASE_AGENT_NAME) defaults to compact output. -q forces compact and -v forces streaming. Recording failures warn once and still execute the child exactly once without inventing a durable id. Catalog, usage, and missing-run errors exit 2. sase tool run returns the child's exit code, or 128+signal.

Each recorded run captures a bounded pre/post fingerprint of configured repositories, input globs, allow-listed environment names, and toolchain probes. It also samples host load at start, roughly every ten seconds, and finish. sase tool show reports evidence completeness, whether complete fingerprints prove an input mutation, dirty-path counts, toolchain summaries, and samples. A missing repository/input, timed-out probe, unsupported host field, or observation budget exhaustion remains explicit incomplete evidence; SASE never substitutes zero or claims mutated_input from incomplete fingerprints.

For definitions with stages: run_silent, nested tools/run_silent calls append stage events to the same run. Compact completion and sase tool show render that timeline, including repeated or overlapping stages and the interval-union time not attributed to any stage. -j exposes the complete stage, fingerprint, and sample records. -l is a different mode: it replays retained stdout and stderr without claiming a total ordering between those streams.

LAST is the newest native result for this project and definition. TYPICAL is the observed median duration of at most 30 normally exited native runs within 30 days. Missing samples render as an em dash, never as zero or an ETA.

sase disk

Disk commands inspect SASE-created bytes by owner and delegate cleanup back to those owners. With no subcommand, sase disk defaults to sase disk list.

Command Flag / argument Values Description
sase disk list -j, --json flag Emit owner, horizon, size, path, and unowned-stray rows as JSON.
sase disk reap -a, --apply flag Run owner cleanup passes instead of previewing them.
sase disk reap -j, --json flag Emit the delegated cleanup plan or execution result as JSON.
sase disk reap -p, --project project name Limit project-scoped artifact and workspace cleanup to one project.

Unowned Cargo-shaped strays are listed for human action and are never deleted by sase disk reap.

The sase disk list table shows each row's owner, section, size, coverage, horizon, and path. Managed-temp rows cover every registered root writers actually used, not only the effective one. Workspace checkouts get one row per project plus sub-rows for .git/objects, .pytest_cache (including sase-visual), sase/repos, .venv, and in-tree target/, sized before the generic stray walk so budget exhaustion can only clip that walk. Unowned rows are highlighted; a row whose coverage is partial or unresolved means the owner could not account for every byte. When coverage is partial or the filesystem is under pressure, an unattributed row reports filesystem used minus attributed bytes on the filesystem holding SASE_HOME. When rows share physical storage (for example workspaces that borrow primary Git objects), the size cell also shows the exclusively counted bytes, and the footer reports the physical total, the logical row total when it differs, the overall coverage status, and any partial-scan diagnostics.

sase disk reap runs, or previews, the owner passes in order: managed-temp reaping, proc runtime cleanup, ACE-run artifact retention (the same owner as sase artifact prune-runs), and workspace Git object compaction. It exits 1 when any owner step is blocked, fails, or cannot report a trustworthy result, and 0 otherwise. Because ACE-run deletion is currently preview-only, the artifact-run step reports blocked under --apply.

sase workspace

Workspace commands inspect and maintain the managed checkout registry for the inferred project, or for the project named by -p/--project. With no subcommand, sase workspace defaults to sase workspace list with default options. Use sase workspace list -p <project>, sase workspace list --all, or sase workspace list --json when passing list flags.

Command Flag / argument Values Description
sase workspace list -p, --project project name Query a project other than the one inferred from the current directory.
sase workspace list -a, --all flag Inventory registered workspaces across every enabled and disabled project.
sase workspace list -j, --json flag Emit a machine-readable JSON object.
sase workspace path workspace_num integer Workspace number to resolve; 0 is the primary checkout and managed claims normally start at 10.
sase workspace path -p, --project project name Query a project other than the inferred one.
sase workspace cleanup -p, --project project name Clean a project other than the inferred one.
sase workspace cleanup -s, --stale flag Remove unclaimed managed checkouts older than workspace.cleanup_ttl_days.
sase workspace cleanup -i, --include-shares flag Also consider workflow-share managed checkouts for removal.
sase workspace cleanup -n, --dry-run flag Report planned removals without touching the filesystem.
sase workspace compact -p, --project project name Compact a project other than the inferred one.
sase workspace compact -n, --dry-run flag Report planned Git object compactions without touching config or objects.
sase workspace compact -j, --json flag Emit the compaction plan or result as a machine-readable JSON object.
sase workspace compact workspace_nums integer list Optional registered workspace numbers to compact; omit to scan all numbered checkouts.
sase workspace repair -p, --project project name Repair a project other than the inferred one.
sase workspace repair -n, --dry-run flag Report registry/filesystem and SASE-managed alternate reconciliation without writing.
sase workspace migrate -p, --project project name Migrate a project other than the inferred one.
sase workspace migrate -t, --to xdg-state Target managed root policy for migration.
sase workspace migrate -s, --symlink-transition flag Leave <primary>_<num> symlinks pointing to migrated managed checkouts.
sase workspace migrate -f, --finalize flag Remove transition symlinks left behind by a prior migration.
sase workspace migrate -n, --dry-run flag Report planned migration or finalization actions without touching files or the registry.

For built-in bare-git projects, sase repo open may initialize generated SDD guide files in the primary checkout before materializing a numbered workspace. sase workspace list and sase workspace path remain read-only and do not run SDD initialization.

sase bead

With no subcommand, sase bead defaults to sase bead list. Commands that take existing bead IDs route a full ID to the enabled project whose bead store owns it, while shorthand suffixes stay in the current project; see CLI Reference and Bead ID Arguments. The tables below cover the most-used subcommands; Beads documents every subcommand.

Flag Values Default Description
subcommand +1, blocked, close, create, dep, doctor, epic-symbols, history, init, list, note, onboard, open, pages, ready, ref, resolve-conflicts, rm, search, show, snooze, stats, sync, sync-external, task-type, update, work list Bead subcommand

sase bead create

Flag Values Default Description
-t, --title string (required) Issue title
-w, --reason string (required) Why the bead was filed. Trimmed, non-blank, at most 2000 characters. @<path> reads the text from a file. See Creation Reason.
-T, --type string (required) plan(<file>), plan(<file>,<parent>), phase(<parent_id>), or task(<slug>). Feature flags use sase flag new
-f, --field k=v - Task-type field value; repeatable. @<path> reads the value from a file
-d, --description string - Issue description
-a, --assignee string - Assignee name
-m, --model string - Epic land-agent, phase-worker, or task-worker model
-R, --ref artifact reference - Artifact reference to attach; repeatable
-z, --size xsmall, small, medium, large, xlarge - Phase/task size; required for new task beads and rejected for plan beads. Phases use model and plan-first routing, tasks use model routing only
-r, --tier plan, epic - Plan-bead tier; invalid for phase and task beads
-c, --patch, --changespec Patch name - Attach Patch metadata to a plan bead; --changespec is legacy-compatible
-b, --bug-id string - Bug ID for the attached Patch; requires --patch or --changespec
-x, --external-ref string - Project-qualified external issue identity, e.g. bug:sase#42

sase bead list

Flag Values Default Description
-c, --color auto, always, never auto Color mode for compact output
-f, --format compact, json, full compact Output format
-n, --limit non-negative integer (unlimited) Maximum beads to print; closed listings default to 20, 0 means all
-S, --since DATE - Only beads created at or after DATE
-s, --status all, open, claimed, ready, snoozed, in_progress, closed open, claimed, ready, snoozed, in progress Filter by status (repeatable); all selects every status. With no match and no --status, closed beads are listed instead
-T, --task-type catalog slug or untyped - Filter by task type (repeatable); untyped selects legacy beads
-r, --tier plan, epic - Filter by plan-bead tier (repeatable)
-t, --type plan, phase, task - Filter by issue type (repeatable). Flag beads are tasks; use -T flag
-u, --until DATE - Only beads created at or before DATE
Flag Values Default Description
query string (required) Literal non-empty text to search for
-c, --color auto, always, never auto Color mode for compact output
-f, --format compact, json, full compact Output format
-e, --regex flag - Treat the query as a case-insensitive regular expression unless it starts with (?-i)
-n, --limit non-negative integer (unlimited) Maximum results to print; 0 also means unlimited
-s, --status open, claimed, ready, snoozed, in_progress, closed - Filter by status (repeatable); all statuses are searched by default
-T, --task-type catalog slug or untyped - Filter by task type (repeatable); untyped selects legacy beads
-r, --tier plan, epic - Filter by plan-bead tier (repeatable)
-t, --type plan, phase, task - Filter by issue type (repeatable). Flag beads are tasks; use -T flag

sase bead task-type

With no subcommand, sase bead task-type defaults to sase bead task-type list.

Flag / argument Values Default Description
list Colored catalog table; -a/--all includes uncreatable
show <slug> Full spec, fields, template, triage, and provenance
-j, --json flag - Machine-readable output on list and show

sase bead epic-symbols

Flag Values Default Description
id string (all) Optional bead ID; omit to list every entry
-c, --color auto, always, never auto Color mode for compact output
-f, --format compact, json compact Output format

sase bead read

Flag Values Default Description
ids string (required) One or more IDs; <epic-id>.. expands the epic plus direct children
-c, --color auto, always, never auto Color mode; applies to --format full and compact
-f, --format compact, json, full full Output format. Compact never expands artifact links
-N, --no-links flag off Skip artifact-link neighborhood resolution and omit link sections / JSON
-p, --pager auto, always, never auto Page long terminal output
-P, --project project key, name, or alias current project Resolve every ID against one enabled project's bead store, including shorthand suffixes
-r, --reason string (required) Non-empty reason for the audited bead read
-s, --style auto, plain, rich auto Styling level for --format full
-w, --wrap integer >= 20, auto, none, 0 88 Prose wrap width for the filing reason, description, notes, link reasons, and evidence

sase bead touched

Flag Values Default Description
agent string (required) Agent name, globalized or local
-j, --json flag off Machine-readable rows with verbs, actors, and read_reasons
-l, --limit integer >= 0 unlimited Maximum beads to print; 0 means unlimited
-v, --verb verb (all) Only show beads touched with this verb (repeatable); unknown verbs exit 2

sase bead show

Flag Values Default Description
ids string (required) One or more IDs; <epic-id>.. expands the epic plus direct children
-c, --color auto, always, never auto Color mode; applies to --format full and compact
-f, --format compact, json, full full Output format. Compact never expands artifact links
-N, --no-links flag off Skip artifact-link neighborhood resolution and omit link sections / JSON
-p, --pager auto, always, never auto Page long terminal output
-P, --project project key, name, or alias current project Resolve every ID against one enabled project's bead store, including shorthand suffixes
-s, --style auto, plain, rich auto Styling level for --format full
-w, --wrap integer >= 20, auto, none, 0 88 Prose wrap width for the filing reason, description, notes, link reasons, and evidence

sase bead open

Flag Values Default Description
id string (required) Issue ID to reopen

sase bead update

Flag Values Default Description
ids string (required) One or more full or shorthand issue IDs
-s, --status open, claimed, ready, in_progress, closed - Change status; ready is task-only. Use sase bead snooze to snooze
-t, --title string - Change title
-d, --description string - Change description
-n, --note string - Append an attributed note to each issue
-D, --design path - Change design path; all types accepted
-a, --assignee string - Change assignee
-m, --model string - Change launch model; '' clears it
-b, --remove-by YYYY-MM-DD/release - Extend one flag task bead's thresholds
-z, --size xsmall, small, medium, large, xlarge - Change phase/task size
-r, --tier plan, epic - Change plan-bead tier
-x, --external-ref string - Set the project-qualified external issue identity, e.g. bug:sase#42
-X, --clear-external-ref flag - Clear the external issue identity

sase bead close

Flag Values Default Description
ids string (required) One or more IDs; exactly one epic ID with --phases
-f, --force flag - Sweep unfinished descendants; needs both below
-P, --no-push flag - Commit the close locally but skip the post-commit push
-n, --note string - Attributed note appended to each listed issue
-p, --phases number/range list - Close numbered phase beads of the target epic
-r, --reason string - Optional close reason text
-R, --resolution canceled, done, superseded done How this bead was resolved

sase bead rm

Atomically removes the requested issues and the recursive union of their descendants. Every requested ID must exist; overlapping or repeated selections remove each issue only once. Removal is irreversible.

Flag Values Default Description
ids string (required) One or more issue IDs

sase bead note

Flag Values Default Description
id string (required) Full or shorthand issue ID
text string - Note text; a single @<path> token reads it from a file. Required unless --remove is given
-a, --author string current agent, else owner Author recorded on the entry
-e, --edit note ordinal N - Rewrite note N as numbered by sase bead read; ordinals shift after any edit or removal
-x, --remove note ordinal N - Retract note N; sase bead history keeps the retracted record

sase bead dep

Bare sase bead dep delegates to sase bead dep list. Mutations reject dependency edges that would cross bead stores before writing.

Form Flag / argument Values Default Description
dep list id string store-wide Optional issue whose edges to list
dep list -d, --direction both, in, out both Edges to show
dep list, dep tree -c, --color / -f, --format auto\|always\|never / compact\|full\|json auto / compact Color mode and output format
dep list, dep tree -s, --status open, claimed, in_progress, closed all when scoped; open, claimed, ready, snoozed, in progress store-wide Filter by status (repeatable); current --help text omits ready and snoozed from the runtime default
dep list -n, --limit non-negative integer - Maximum beads to print; 0 means unlimited
dep tree id string store-wide Optional issue to walk from
dep tree -d, --direction both, in, out out Walk dependencies (out), blockers (in), or both
dep tree -L, --levels non-negative integer unlimited Maximum levels to descend; 0 means unlimited
dep add issue string (required) Issue that depends
dep add depends_on string (required) Issue being depended upon
dep rm issue depends_on... strings (required) Remove one or more dependencies from an issue

sase bead sync

Flag Values Default Description
-s, --status flag - Check sync status without committing

sase bead work

Flag Values Default Description
targets bead IDs or plan paths (required) One or more epic/task beads or validated epic plan files, processed in order until the first error
-a, --artifacts-dir directory - Back-fill planner artifacts after each approved epic; plan-file targets only
-c, --capacity positive integer omitted Epic-only per-launch capacity budget; 1 runs alone; a heavier macro weight floors that segment
-C, --cl-name Patch name - Approved epic Patch name applied per plan-file target
-n, --dry-run flag - Preview the epic wave plan or task prompt without mutating files, beads, or agents
-j, --json flag - Print one result object per processed target as JSON Lines and imply --yes-to-all
-P, --no-push flag - Commit checkpoint state locally but skip post-commit pushes
-p, --parent bead ID or top-level - Override a plan file's parent_bead, including forcing an unparented epic; plan-file targets only
-w, --wait comma-separated names and bead=<id> - Agent names and bead dependencies the launched phases wait for
-y, --yes flag - Skip only the launch confirmation prompt
-Y, --yes-to-all flag - Skip both destructive-cleanup and launch confirmation prompts

Multiple sase bead work targets are non-atomic: earlier successes are not rolled back, later targets are not prevalidated, and every command-wide flag is checked again for the current target.

SDD repository and plan commands

SDD initialization/path resolution lives under sase repo; artifact browsing and prompt/plan link maintenance live under sase plan. Link commands accept -p/--path, which may point at an SDD root or a project root. Bare sase plan links defaults to its list child.

Command Flags Description
sase repo init -c/--check, -d/--diff, -C/--no-commit Initialize configured sidecars and repository wiring for the current repository
sase repo path REPO -e/--ensure, -p/--project, -w/--workspace Print a primary or sidecar path; optionally materialize it
sase plan links [list] -p/--path, -j/--json List prompt/plan artifact links and bidirectional status
sase plan links refresh -p/--path, -P/--plan, -j/--json, -w/--write Dry-run (or, with --write, commit) reconciliation of PARENT, BEAD, AGENTS, and COMMITS header sections
sase plan links repair -p/--path, -w/--write Infer unambiguous prompt/plan pairs and optionally write fixes
sase plan links validate -p/--path, -j/--json, -q/--quiet, -W/--show-warnings Validate a plan's own metadata and PROMPT bullet
sase plan search -k/--kind, -o/--source, -f/--format, plus query/date/status filters Search or browse tale, epic, prompt, and document-sidecar artifacts

sase validate

sase validate is the top-level portable SASE validation command. It takes no options and runs, in order, sase doctor -C plugins.required (so a missing required plugin is never reported as spurious generated-file drift), the explicit sase init memory --check, sase init repo --check, and sase init skills --check surfaces, sase doctor -C config.file_hooks, sase plan links validate, and sase agent prompts validate. It prints one ok, skip, or fail line per check and exits non-zero if any check fails; the prompt-archive check reports skip rather than failing when no agents-sidecar prompt archive context is available. It deliberately leaves the machine-local Config and machine planners to bare sase init --check and sase doctor, so clean CI hosts do not need a synthetic machine identity. The command can still fail on user/home memory or skill deployment drift even when repository-local SDD validation passes.

A check can pass and still have something to say. When a check that exits 0 prints its own Warnings: section — sase init skills --check deferring a chezmoi redeploy is the common case — sase validate collects those lines and reprints them under a single Warnings: block, after the per-check status lines and before any failure output. The block is informational: it does not change the exit code, and it is separate from the stdout dump that a failing or skipped check still produces.

sase doctor

Runs the read-only support diagnostics bundle for the active runtime, configuration, provider setup, project/workspace state, bead store, agent index, and telemetry when configured. Default mode is bounded and safe to run before asking for help; deep mode adds slower read-only checks.

Flag Values Default Description
-j, --json flag - Emit the schema_version: 1 JSON support report.
-v, --verbose flag - Show every check plus bounded details in human output.
-D, --deep flag - Include slower read-only deep checks.
-s, --strict flag - Exit non-zero for warnings as well as errors.
-L, --list-checks flag - List registered default and deep check ids without running them.
-C, --check id/group repeat Run only the selected check id or group; may be passed multiple times.
-F, --fix-duplicate-blocks flag - Preview and, after confirmation or -y, keep one block per Patch name in every ProjectSpec.
-R, --fix-primary-sidecar-links flag - Preview and, after confirmation or -y, restore stranded link-index deletions in primary-nested sidecars.
-p, --project string infer Inspect a named project when doctor cannot infer one from the checkout.
-y, --yes flag - Apply requested repairs without an interactive confirmation.

Use sase doctor -L to list targeted check IDs. Useful focused checks include runtime, llm.default, plugins.required, plugins.resources, beads.task_types, project.junk_directories, project.primary_sidecar_link_dirt, workspace.missing_checkouts, workspace.occupancy_conflicts, and config.model_macros, config.macro_definitions, config.macro_input_types, and config.macro_directives. The two inventory checks report telemetry-only directories without ProjectSpecs and registered workspace paths missing from disk; both are read-only and provide cleanup/repair guidance. workspace.occupancy_conflicts reports RUNNING-field and occupant-record collisions and never auto-repairs. config.macro_input_types lists unknown type names, the deprecated string alias, and enum choice issues across macro sources. config.macro_directives locates definition files that still use retired directive syntax. agent_holds.stale warns about agent holds whose armer died or whose TTL passed; it reconciles the hold store the same way sase agent hold list does, so the stale records it reports are pruned as a side effect.

Default exit behavior is 0 for OK, WARN, and SKIP, and 1 for ERROR. Attach sase doctor -v or sase doctor -j when asking for help.

sase flag

With no subcommand, sase flag defaults to sase flag list.

Form Flag or argument Description
sase flag disable <key>, -j/--json Persistently disable a registered flag in $SASE_HOME/feature_flags.json. Restarts the running scheduler service proc; a stopped scheduler is left stopped. Restart any separately running sase's TUI. Restart failure does not roll back the saved preference.
sase flag enable <key>, -j/--json Persistently enable a registered flag in $SASE_HOME/feature_flags.json. Same scheduler-proc restart, sase's TUI notice, JSON envelope, and partial-success contract as disable.
sase flag list -j, --json List registered flags, resolved values, provenance, saved vs effective state, beads, and due state.
sase flag new <key>, --when-enabled, --when-disabled, --remove-when, -d/--description, -k/--kind (beta/sunset), -r/--remove-by, -z/--size Create a flag task bead and print the registry entry to paste.
sase flag show <key>, -j/--json Show one flag's full decision, saved value, bead thresholds, diagnostics, and call sites.

Unknown keys on enable/disable exit 2. Store or scheduler-restart failures exit 1; JSON then has "ok": false with the preference still recorded under mutation. --json emits one versioned document with separate mutation and restart objects. Neither command edits portable config files.

A non-empty human sase flag list ends with a compact statistics footer: how many flags were rendered and which kinds they use, how many resolve on versus off, how many decisions came from a layer above the registry default, and whether any loaded removal bead is soon or due. Homogeneous kind or on/off values fold into the count head. For example:

3 flags · 2 beta  1 sunset · 2 on  1 off · 1 overridden · ⧗ 1 soon  ⧗ 1 due

--json keeps schema version 1 and does not include this footer.

new requires a SASE-managed checkout because the registry lives in this source tree. --when-enabled, --when-disabled, and --remove-when are required and each accepts @<path>. -k/--kind is beta (default; off) or sunset (on). There is no --scope.

sase file-hook

With no subcommand, sase file-hook delegates to sase file-hook list. The list shows each valid effective hook's name, description, command, project/sidecar/producer/glob/operation filters, timeout, and contributing config source. Invalid and duplicate hooks are already excluded with configuration warnings, so this is the runtime-effective view rather than a raw config dump.

Form Flag Description
sase file-hook list — Render the effective hook table.
sase file-hook list --json -j, --json Emit machine-readable hook records (schema_version: 4, including filters.producers).
sase file-hook history -n, --limit Show recent producer audits (dispatched, unmatched, or failed). Default 20; 0 means all retained.
sase file-hook history --json -j, --json Emit machine-readable producer audits.
sase file-hook show AUDIT_ID -j, --json Show one producer audit by exact id or unique prefix.

See file_hooks for matching, merge, execution, producer audits, and notification behavior.

sase version

sase version reports the local runtime that the current sase process is using. It does not query PyPI, GitHub, or latest available releases. The inventory always includes the host sase package and the required sase-core-rs Rust core distribution, then adds installed SASE plugin packages discovered through SASE entry points, SASE console scripts, or sase-* distribution names.

The default human output is a compact runtime panel plus a package table with role, effective version, and code directory. Development checkouts use PEP 440 local versions such as 0.1.2+4.g26c39e004 or 0.1.2+0.g26c39e004.dirty. Editable installs prefer source metadata over stale installed distribution metadata, while --verbose and --json expose both values for auditability.

Flag Values Default Description
-j, --json flag - Emit a stable JSON object with schema_version: 1, runtime, and packages.
-v, --verbose flag - Include install type, dist/source versions, git metadata, and plugin signals.

sase var

sase var inspects and publishes SASE agent output variables. Agents publish values with sase var set, which merges named JSON-shaped values into the current run's agent_meta.json["output_variables"]. sase artifact create also publishes the SASE-managed artifacts list there. The stored values appear in sase's TUI Agents-tab OUTPUT VARIABLES metadata panel, Telegram agent-completion messages, indexed agent history, and downstream %wait prompt contexts. Later agents that wait on a producer load that producer's stored values when they start and can render them through the agents Jinja dictionary in prompts and macro workflows.

With no subcommand, sase var prints a delegation notice and runs sase var list.

Form Flags / arguments Description
sase var get -c, -f pretty\|json Show the current agent's output-variable snapshot from SASE_ARTIFACTS_DIR.
sase var get '<AGENT_NAME>' -c, -f pretty\|json, -p, -H Show the newest exact-name historical snapshot. Quote the wrappers.
sase var get SELECTOR [...] -c, -f pretty\|raw\|json\|jsonl Resolve precise values by exact, wildcard, hood, and JSON-path selectors.
sase var list -a, -k, -p, dates, value flags Discover keys and distinct typed values across indexed agent history.
sase var set KEY=VALUE [...] positional assignments Store one or more strings, splitting each assignment at the first =.
sase var set KEY --value TEXT -v, --value TEXT Store one string verbatim, including spaces or newlines.
sase var set KEY --value-file PATH -f, --value-file PATH Read one string as UTF-8 text; use - to read standard input.
sase var set ... --json -j, --json plus a form above Decode supplied values as JSON strings, scalars, lists, maps, or null.

sase var get has three modes. With no target, it reads the current agent's artifact directory directly so recent writes are visible before the agent completes; this form requires SASE_ARTIFACTS_DIR. With exactly one quoted <AGENT_NAME>, it searches indexed history and returns the newest exact-name artifact. Quote the wrappers so the shell keeps them intact, for example sase var get '<build>' --format json. Use --project PROJECT to disambiguate repeated names across projects, where PROJECT may be a display name or alias and may be repeated. --hidden includes hidden indexed agents. An agent with no variables is an empty success; an unknown name is an error. --format pretty renders the same readable block form used in sase's TUI, while --format json emits the variable map as compact machine-readable JSON. Snapshot mode rejects selector-only --format raw / jsonl and an explicit --limit.

sase var list is historical discovery. It groups indexed output variables by key, orders keys by most-recent occurrence, and shows each key's distinct typed values plus the contributing agent names. Repeated filters in one dimension are ORed; different dimensions are ANDed. Major filters are:

  • --agent GLOB / -a: filter by agent-name glob. hood.* includes the hood root.
  • --key GLOB / -k: filter by case-sensitive variable-key glob.
  • --project PROJECT / -p: filter by project display name or alias; repeatable.
  • --hidden / -H: include hidden indexed agents; visible history is the default.
  • --since DATE / -s and --until DATE / -u: filter by launch time using the same date grammar as bead history filters.
  • --value TEXT / -v: case-insensitive substring match over scalar text and canonical JSON.
  • --value-json JSON / -V: exact typed JSON value match after output-variable normalization. It is mutually exclusive with --value.
  • --limit KEYS[:VALUES] / -n: cap returned keys and distinct values per key. The default is 20:5; 0 means unlimited for that dimension; a single number changes only the key limit.
  • --reverse / -r: invert the normal recent-first key and value order.

sase var list --format pretty prints readable grouped blocks. --format json emits a stable envelope with schema_version, the normalized query, limit metadata, and grouped values. --format jsonl emits one compact JSON object per returned distinct value.

With one or more ordinary targets, sase var get retrieves exact values from indexed history with selectors:

[SCOPE.]KEY[PATH ...]

KEY is a variable name or *. SCOPE may be an exact agent name, * for every agent, or HOOD.* for a hood. Unscoped keys choose the newest matching occurrence. Exact-agent selectors choose that name's newest artifact. Global and hood wildcard selectors collapse repeated runs to the newest value per agent name. JSON paths follow the selected value with [INDEX] for lists or ["KEY"] for map keys; dotted map traversal is not accepted. sase var get build.* remains selector mode and is distinct from snapshot-mode sase var get '<build>'.

Examples:

sase var get
sase var get --format json
sase var get '<build>' --format json
sase var get status
sase var get build.status --format raw
sase var get '*.status' --format json
sase var get 'research.*.report["summary"]'
sase var get results[0]

Selector --format pretty prints each match with attribution. --format raw prints one value only and fails unless the selector resolves to exactly one untruncated match; strings print as text, and structured values print as compact JSON. --format json emits a stable envelope with schema_version, query, limit metadata, and matches. --format jsonl emits one compact JSON object per match. Wildcard expansion defaults to 20 matches; --limit 0 is unlimited. --project is repeatable, and --hidden includes hidden indexed agents. --limit applies only to selector wildcard expansion.

sase var set is the mutation command. It is agent-scoped and requires SASE_AGENT=1 and SASE_ARTIFACTS_DIR. Successful writes print the current agent name when known, the stored keys, and the artifact directory.

Keys must be valid Jinja attribute identifiers ([A-Za-z_][A-Za-z0-9_]*). Values may contain spaces, blank lines, newlines, and additional equals signs. The KEY=VALUE form splits only on the first = and preserves everything after it; quote the whole assignment when the shell would otherwise split it. --value likewise preserves exactly the text supplied by the shell, including any trailing newlines. --value-file reads a file or stdin and removes at most one trailing newline after normalizing line endings, which makes files, pipes, and heredocs convenient without discarding an intentional trailing blank line. With --json, JSON whitespace is ignored by the decoder and no trailing newline is removed before parsing:

sase var set 'suites=["unit","integration"]' --json
sase var set cfg --json --value '{"retries":3,"enabled":true}'
sase var set report --json --value-file report.json
sase var set findings --json --value-file - <<'JSON'
[{"file":"src/a.py","severity":"high"}]
JSON

A value may be any JSON string, number, boolean, null, list, or map. Nested map keys may be any non-empty NUL-free string. Map keys are normalized into sorted order for deterministic storage and display; list order is always preserved. Structured values reach Jinja as real containers, so consumers can use attribute/subscript access and loops. Rendering an entire container with {{ agents["build"].cfg }} yields compact JSON, while | tojson remains available for explicit JSON formatting.

An agent may store at most 256 variables. Each string leaf and nested map key is limited to 8,192 UTF-8 bytes; each variable is limited to depth 8, 1,024 total container-plus-leaf nodes, and 65,536 compact encoded JSON bytes. Numbers must be finite and integers must fit the signed 64-bit range. Every string converts CRLF and lone CR line endings to LF and rejects NUL characters. Invalid or oversized values fail visibly instead of disappearing during publication.

Multiple calls merge into the same variable map; later writes for the same key replace earlier values. The command does not update prompts that have already started rendering, so write variables before the producing agent completes and before dependent agents unblock. Downstream prompts read each producer's variables from the single agents dictionary keyed by the producer's stable agent name, e.g. {{ agents["build"].report_path }} (or {{ agents.build.report_path }} for identifier-safe names). Do not store secrets; output variables are persisted in agent_meta.json and shown in sase's TUI and Telegram completion messages.

STOP is a reserved output variable. sase var set stays generic and stores it like any other key, but repeat orchestration interprets it: setting STOP (e.g. sase var set STOP=1) inside a %repeat / %r iteration stops the remaining repeat slots, which finalize as successful skipped slots. null, false, numeric zero, empty strings, empty lists, and empty maps are not-stop; string values 0, false, no, and off are also not-stop case-insensitively after trimming. Any other value stops the chain. STOP affects only repeat-chain continuation; ordinary %wait consumers read it as a normal variable. See Repeat Directive in the macro reference for the full cascade semantics.

sase telemetry

With no subcommand, sase telemetry prints a delegation notice and runs sase telemetry list.

Flag Values Default Description
subcommand cleanup-test-data, health, list, snapshot, status list Telemetry subcommand

See docs/telemetry.md for the full CLI reference including per-subcommand flags.

sase logs

Flag Values Default Description
daterange string (required) Date range to collect (e.g., -7d, 260318, 260315..260318)

Supported date range formats:

  • Absolute: YYmmdd or YYmmddHHMMSS
  • Relative: -Nd (days ago), -Nh (hours ago), -Nm (minutes ago), 0d (today)
  • Ranges: START..END (e.g., -7d..0d); single point means "from that point to now"

The run and event inputs at ~/.sase/logs/runs.jsonl and events.jsonl rotate independently before appending a record would make a non-empty file exceed 2 MiB. Rotation keeps one .1 generation and replaces an older backup; set SASE_RUN_LOG_MAX_BYTES to another byte limit, or 0 for no size rotation. The current sase logs collector reads only the active .jsonl files and skips malformed lines there, so copy the matching .1 files separately when a support bundle must include records from the previous generation.

sase editor

sase editor exposes JSON-over-stdin helper operations for editor integrations. It is intentionally a fixed-operation bridge rather than a generic shell or filesystem API.

Form Input Description
sase editor helper-bridge agent-catalog JSON object on stdin Return active/recent agents and derived session, clan, and tribe prompt targets.
sase editor helper-bridge finalizer-catalog JSON object on stdin Return configured %final completion rows from effective finalizer config without loading providers.
sase editor helper-bridge macro-catalog JSON object on stdin Return the structured macro catalog; accepts the same schema as the mobile macro-catalog helper operation.
sase editor helper-bridge snippet-catalog JSON object on stdin Return the composed sase's TUI snippet registry used by sase lsp and editor completion clients.
sase editor helper-bridge vcs-repo-catalog JSON object on stdin Return repository completion candidates for a VCS workflow and namespace.

The finalizer-catalog request is {"schema_version":1} with an optional project hint; unknown fields are ignored. The response is a compact status/message/entries envelope. Malformed finalizer configuration returns status: error and no rows, and the builder never loads provider code.

The agent-catalog request is just {"schema_version":1}; it has no project filter and reads the cross-project agent snapshot. Ordinary rows are de-duplicated by name and include status and project, with kind: agent for agents and kind: monitor for monitors. When group metadata is available, additive session, clan, and @tribe rows include kind, member_count, and display-ready detail; clan rows also include aggregate status. For the 20 most recently active sessions, SASE tries to enrich detail with the associated plan or bead's kind, structure, and title, and to add Markdown documentation carrying the goal, phase list, or parent/task context plus a session status footer. Unresolved or older sessions keep the plain member-count detail, and enrichment failure never removes ordinary rows; see Editor Integration: Helper Bridge for the exact fallback ladder. The structured macro catalog includes insertion metadata (insertion, reference_prefix, kind), typed argument metadata, display/source fields, and definition_path when SASE can resolve a real file to jump to.

The snippet catalog uses the same source ordering as sase's TUI: macros marked with snippet front matter plus user-defined ace.snippets, with ace.snippets winning on trigger collisions. It also includes the generated initial-capital aliases (foo → Foo), so editor completion and the native fallback expose exactly the same trigger/template pairs as the TUI.

sase file

With no subcommand, sase file defaults to sase file list.

Form Flags Description
sase file list -p/--path, -t/--token Emit JSON filesystem completion candidates rooted at --path and filtered by the cursor token.

sase file-history

With no subcommand, sase file-history defaults to sase file-history list.

Form Flags Description
sase file-history list none Emit the recency-ordered file-reference history as a JSON array.
sase file-history delete -p/--path Remove one entry from the file-reference history.

sase gate

Create, inspect, answer, and manage durable command-backed gates and their optional gate-turn session members.

Form Principal flags Description
sase gate act -i/--id, -k/--kind, -o/--operation, -I/--input, -j/--json Run one repeatable declared action without answering the gate
sase gate answer -i/--id, -k/--kind, repeatable -o/--option, -s/--set, -O/--option-input, -I/--input, -f/--feedback, -d/--detach / -D/--no-detach, -r/--resume / -R/--restart, -j/--json Answer one branch; --resume continues a partial option run or an unfinished coder handoff
sase gate cancel <turn> or -i/--id -k/--kind, -r/--reason, -j/--json Cancel a pending gate or turn; launches no follow-up
sase gate create -G/--turn, -n/--next, -f/--next-fork, -m/--next-model, repeatable -N/--next-output, -o/--origin-agent, -g/--turn-status, -E/--turn-stop-status, -p/--panel, -P/--panel-icon, -s/--sender, repeatable -t/--tag Create a gate from JSON on stdin, optionally handing an agent to a gate turn
sase gate list -a/--all, -l/--agent, -p/--project, repeatable -s/--state, -n/--limit, -f/--format, -j/--json List pending gate turns newest first; --all includes settled turns
sase gate show [turn] or -i/--id -k/--kind, -j/--json Show branches, inputs, actions, runtime state, workspace claim, and follow-up disposition
sase gate wait -i/--id, -j/--json, -k/--kind, -t/--timeout Wait for a gate; exits 0 answered, 3 cancelled, 4 timeout, 5 failed

Gate creation accepts one option query, a required complete primary_branch, an options list with configurable labels, icons, default selections, and feedback modes, plus optional groups metadata for AND-branch submit controls. It returns a stable JSON descriptor with the request identity, owned paths, continuation/auto state, and hashes. sase gate wait -j emits status, selected_option_ids, feedback, and response_path; failed execution also emits failure and failure_recovery with the error report path and exact safe recovery commands. A CLI timeout can shorten but not extend the request timeout.

--turn creates a processless session member that owns the pending decision and ends an agent-side creator's turn. --next is the default answered-branch follow-up prompt; branch-specific policy may override or suppress it, and non-answered terminal branches need their own explicit follow-up. --next-fork session|turn|none chooses inherited context, --next-model pins the successor model, and repeatable --next-output none|results|tail|file chooses what gate-command evidence reaches it. Use sase gate wait from non-agent automation; an agent that created a turn-backed gate must hand off rather than hold a provider process open. -p/--panel places the gate's notification in a named notification-panel tab and requires -P/--panel-icon. For sase gate answer, per-option input (-s/--set, -O/--option-input) and the legacy shared -I/--input value are mutually exclusive; a gate turn answers through a supervised background proc unless -D/--no-detach is given.

sase sudo

sase sudo creates typed sudo request gates and answers them from a controlling terminal through SASE's dedicated sudo runner; see Sudo Requests. Every subcommand requires the agent_sudo_requests beta flag and fails with a feature_disabled error otherwise. With no subcommand, sase sudo defaults to sase sudo list.

Form Flags Description
sase sudo request -j/--json, -o/--origin-agent Read one JSON request object from stdin, create the gate turn, and print its descriptor; an agent caller then hands off.
sase sudo list -a/--all, -j/--json, -l/--limit, -p/--project List pending sudo gate turns; --all includes settled ones.
sase sudo show <ID> -j/--json Show one sudo gate by gate id or turn ref.
sase sudo answer <ID> -u/--run or -a/--approve, -d/--deny, repeatable -c/--command, -f/--feedback, -r/--resume or -R/--restart, -j/--json Approve and run, or deny, one sudo gate.

--run and --approve both authenticate and run the reviewed commands; -c/--command limits the run to selected reviewed command ids. Without --run, --approve, or --deny, an interactive terminal asks Approve sudo request? [y/N], and a non-interactive caller gets a tty_required error. Approval always needs a controlling terminal, so the gate stays pending when none is available; denial runs no privileged command and works headlessly. For a machine-targeted request, approval runs the target's sase sudo exec over ssh -t. -r/--resume and -R/--restart are accepted for parity with sase gate answer, but both currently rerun the full selected command set; neither skips commands that already completed. To avoid repeating one, explicitly select only the remaining command ids with -c/--command. The hidden sase sudo exec subcommand is the target-side runner entrypoint.

sase lsp

Starts the macro language server over stdio for editor integrations. SASE_MACRO_LSP_CMD can override the server command during development. Without that override, sase lsp uses the current Python environment's bin/sase-macro-lsp, then sase-macro-lsp from PATH, then the newer debug/release binary from a sibling ../sase-core checkout, then falls back to cargo run from that sibling checkout when Cargo is available. Full editable-install SASE updates reinstall the server into the uv-tool venv when pulled sase-core commits change.

Flag Values Default Description
-V, --version flag - Print the macro LSP version and exit

sase path

Flag Values Default Description
name macros-dir, macros-schema, macros-collection-schema, config-schema (required) Which path to print

sase usage

Bare sase usage defaults to sase usage list. Listing is strictly offline: it reads the cached subscription-usage store and never starts AXE, logs in, or calls a provider CLI/API. Output is Rich on an interactive color terminal and automatically becomes plain for redirected output, NO_COLOR, or a dumb terminal.

Form Flags Description
sase usage list -j/--json, -P/--plain, -p/--provider, -v/--verbose Render cached observations; provider filters are repeatable.
sase usage refresh -b/--background, -j, -P, -p/--provider, -v/--verbose Submit or join bounded durable refreshes; foreground waits and then renders the updated cache.

--json and --plain are mutually exclusive. Background mode returns after submission and renders the receipt instead of the cache. Unknown providers are usage errors. Collection requires llm_provider.usage_metrics.enabled; see Subscription Usage.

sase notify

With no subcommand, sase notify defaults to the compact sase notify list view. Use sase notify list for JSON, limit, query, unread, dismissed, or the clearest sender/tag filtering form. Use sase notify create to write a raw, non-privileged notification from stdin JSON. Use sase gate create and sase gate wait for command-backed gates.

Form Flags Description
sase notify -s/--sender, -t/--tag Shortcut for sase notify list with default compact output
sase notify +1 [ID] NOTE -k/--dedup-key, -s/--sender Append corroboration by exact/unique ID prefix or sender-scoped dedup key
sase notify create -k/--dedup-key, -p/--plus-one-note, -S/--supersedes, -s/--sender, -t/--tag Create a raw notification or atomically append to a matching deduplicated row
sase notify list -j/--json, -l/--limit, -q/--query, -t/--tag, -s/--sender, -u/--unread, -a/--all List recent notifications; -j includes plus_ones, count, and dedup_key
sase notify show -i/--id, -f/--format (markdown or json) Show one notification by id; defaults to markdown
sase notify rules -e/--explain ID, -j/--json List merged delivery rules in evaluation order, or explain one row's toast and sound

Raw creation accepts JSON icon, tags, and silent fields plus repeatable -t/--tag; icons must be one emoji or display glyph, and CLI tags are appended to JSON tags, then normalized and deduplicated. Raw creation cannot create a registered privileged gate action. The query form, sase notify list -q, also matches tags, and sase notify list --tag <tag> filters to notifications with that exact normalized tag.

notify +1 never changes read, dismissed, muted, snoozed, ordering, or delivery state. When notify create uses a dedup key, --plus-one-note is required: it is appended on a match, while a miss creates the row. --supersedes identifies an old sender-scoped key to annotate and dismiss only when that miss creates the replacement; it has no effect when the current dedup key already matches a row. See Notification +1 Evidence.

sase plan

With no subcommand, sase plan defaults to the sase plan list dashboard.

Form Flags Description
sase plan approve [PLAN] -n/--dry-run, -k/--kind, -m/--model, -P/--project, -p/--prompt, -w/--wait Approve a pending proposal or a plan with no live approval gate.
sase plan / sase plan list -j/--json, -n/--limit, -s/--status, -t/--tier List pending proposals, approvals, and inferred rejected rows.
sase plan propose <plan_file> - Submit a Markdown plan file for approval from the /sase_plan skill.
sase plan reject [PLAN] - Reject one pending proposal by name, <shard>/<name>, path, plan: ref, planner agent, or notification ID/prefix.
sase plan search [query] -f/--format, -k/--kind, -s/--status, -o/--source, -r/--sort, -A/--since, -B/--until, -n/--limit, -c/--color Search SDD and machine-local Markdown plans.
sase plan show [target] -c/--color, -f/--format full\|compact\|json\|raw, -t/--target, -w/--wrap Resolve one plan by path, plan: reference, pending selector, slug, or bead id and render it.
sase plan validate <plan_file> -e/--explain, -j/--json, -q/--quiet Validate using the plan's authored tier: tale or tier: epic schema.
sase plan links ... see SDD repository and plan commands List, refresh, repair, or validate SDD prompt/plan links.

sase plan list prints a Rich dashboard by default and emits a stable JSON projection with summary, proposed, approved, and rejected keys when -j/--json is set. Repeat -s/--status with approved, proposed, or rejected to render or serialize only those sections; unrequested JSON section keys are omitted, while summary counts continue to describe the full collected view. -n/--limit controls the maximum rows in each Approved and Rejected history section (default 10, with 0 meaning unlimited). Proposed rows are always shown in full, leading with the plan name (shortest unique form, dim id_prefix below) and a ready-to-paste approve/reject hint line. -t/--tier composes with both filters. Proposed --json rows carry the same name field alongside id_prefix. The JSON summary includes status_filter, tier_filter, and a non-default limit when applicable, plus approved_scan_truncated if a finite artifact scan may have omitted older approvals.

Use the Proposed row's plan name as the selector for sase plan approve or sase plan reject (the row's id_prefix still works); omitting the selector is valid only when exactly one pending proposal exists. The Rejected rows are inferred from archived proposal files that are not represented by current proposed or approved state, so they are useful for history but are not actionable selectors. Omitting --kind uses the plan's authored tier; explicit choices override it and tale/epic targets are validated before the proposal is consumed. Approval kind approve runs the coder without asking the runner to commit an SDD plan, tale commits the plan as an SDD tale and then runs 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. The -m/--model flag applies to the follow-up agent; -p/--prompt adds extra coder instructions only for the approve and tale paths. Use -w/--wait with comma-separated agent names and bead=<id> entries to hold the approved coder or launched epic phases until those dependencies finish. sase plan reject writes the rejection response first, then attempts the same durable cleanup path as TUI no-feedback rejection when the matching planner row is still discoverable.

sase plan search [query] scans plans in the resolved SDD store (the repo source) and the machine-local ~/.sase/plans/ archive. The query is a literal case-insensitive substring; omit it to browse and filter. --format accepts compact, full, json, or markdown; --kind is repeatable and filters SDD-store artifacts to a plan tier (tale or epic), prompt, or a configured document-sidecar role such as research (an unknown kind is a usage error that lists the valid ones); --status is repeatable and filters frontmatter status to wip or done; --source selects all, repo, or local; --sort selects relevance, recent, or title (defaulting to relevance with a query and recent without one); --since/--until accept YYYY-MM-DD, YYYY-MM, YYYYMM, or relative durations such as 14d; and --limit defaults to 20, with 0 printing all matches.

sase plan validate <plan_file> infers the validation schema from the authored tier; it no longer accepts -t/--tier. --explain prints tier-specific authoring guidance before human results or adds it to the JSON envelope, while --quiet suppresses only the successful human summary. See Plan Frontmatter Schema and Validation for diagnostics and exit codes.

sase artifact

sase artifact creates, discovers, inspects, resolves, opens, and repairs indexed artifacts. Bare sase artifact delegates to sase artifact list, and sase artifact-file remains a compatibility alias for the whole group.

sase artifact create is intended for code agents running with SASE_AGENT=1 and SASE_ARTIFACTS_DIR set. It copies a generated file into persistent SASE artifact storage and associates it with the current agent so the Agents tab can open it with A, even after the agent has been dismissed and revived. -k/--kind accepts chat, plan, image, markdown, pdf, or file and defaults to a kind inferred from the file extension. -m/--move opts into removing the source after it is stored, -l/--label sets the display label (default: the source file name), and -b/--bead [ID] attaches the new file: reference to a bead (a bare -b uses the agent's SASE_BEAD_ID bead). On success the command prints the artifact's id:, absolute source:, stored path:, durable ref: (file:<id>), and var: artifacts[<index>] (bead: follows with --bead). It also records the artifact in the agent's SASE-managed artifacts output variable. Only create is agent-gated; every other artifact subcommand works outside an agent run.

Form Flags Description
sase artifact create -b/--bead, -k/--kind, -l/--label, -m/--move, -p/--path Store one explicit artifact for the current agent
sase artifact doctor -f/--fix, -v/--verify Report file-index and link-graph health; repair projections/renames and backfill enrichment with --fix, verify hashes with --verify
sase artifact link add (positionals source_ref, relation, target_ref, why) Add or rewrite one typed artifact link
sase artifact link import-indexes optional attestation, -a/--apply, -j/--json Preview or apply the attested, resumable legacy links/ to immutable-event cutover
sase artifact link list -d/--direction, -j/--json, -l/--limit, -o/--origin, -R/--relation, -s/--source, optional ref List recent artifact links or one artifact's neighborhood
sase artifact link migrate-notes -a/--apply, -j/--json Convert parseable historical RELATED: bead notes to typed links
sase artifact link relation list / show <slug>, -j/--json Inspect the closed relation registry, direction, examples, and recommended endpoint kinds
sase artifact link rm -R/--relation, (positionals source_ref, target_ref) Remove typed links between two artifacts
sase artifact link suggest optional reference, -j/--json, -l/--limit Print write-free missing-link suggestions backed by deterministic evidence
sase artifact list -a/--agent, -e/--explicit, -j/--json, -k/--kind, -l/--limit, -p/--project, -q/--query, -s/--since, -u/--unused List indexed artifacts newest-first
sase artifact open (positional reference) Open a resolved reference with a kind-appropriate viewer
sase artifact pane show -j/--json, (positional pane_id) Explain one configured Artifacts pane contract: every capability ON or OFF with its rule, declared fact, and reason; unknown pane ids fail
sase artifact path (positional reference) Print the one absolute path a reference resolves to
sase artifact prune -a/--apply, -b/--before, -g/--keep-generations, -j/--json, -k/--kind, -l/--limit, -m/--min-size, -p/--project Plan retention, then move selected automatic rows to restorable trash only with --apply
sase artifact prune-runs -a/--apply, -i/--index-path, -j/--json, -l/--limit, -m/--keep-recent-months, -p/--project, -r/--projects-root Preview removal of old ace-run directories and empty out-of-range shards; --apply currently refuses and exits 1 because ACE-run deletion is preview-only
sase artifact read -f/--format, -n/--lines, (positionals reference, reason) Print one artifact as audited context and optionally record a read link
sase artifact reclaim -a/--apply, -d/--max-history-scan, -j/--json, -l/--limit, -p/--project Convert eligible stored automatic rows to verified VCS-backed rows only with --apply
sase artifact show -j/--json, (positional reference) Show metadata, resolution, and consumption
sase artifact stats -j/--json, -p/--project, -t/--top Report store economics, protection-source evidence, trash occupancy, and default-policy selection
sase artifact trash / trash list -j/--json, -l/--limit List trash entries newest-first, flagging entries past the grace period
sase artifact trash purge -a/--all, -j/--json Permanently delete entries past the grace period, or every entry with -a
sase artifact trash restore -j/--json, (positional reference) Restore one entry's payload and complete index row by entry id or artifact ref

list filters: -k/--kind is repeatable and accepts the artifact kinds above; -l/--limit defaults to 50 and 0 means unlimited; -p/--project accepts a display name, alias, or canonical key and exits 2 for an unknown project; -q/--query is a case-insensitive substring match over label and paths; -s/--since accepts the same DATE forms as sase plan search (YYYY-MM-DD, YYYY-MM, YYYYMM, or relative 14d / 3w / 2m); and -u/--unused shows only artifact files with no recorded file:<id> consumption. Pretty output is a Rich panel with KIND, REF, LABEL, PROJECT (display name), AGENT, SIZE, and CREATED columns; -j/--json emits every record field — including sha256, size_bytes, and mime_type — plus the rendered ref.

show, path, and open accept canonical artifact references (file:, stitch:, patch:, bead:, agent:, and document kinds such as plan:, research:, or designs:). Historical commit:, chat:, bug:, and plans: aliases remain readable for compatibility. Document kinds and file: preserve supported #L, #page=, and #t= fragments; bead: and agent: reject fragments because they resolve to regenerated pages whose anchors can drift. bead: resolves to the generated bead page in the current project's beads sidecar, and agent: resolves to the generated agents/<global-name>/README.md page in the current project's agents sidecar, so path and open work for both after sase bead pages refresh or sase agent sync has published the page. Bead and agent resolution is intentionally scoped to the reference context's single project; foreign bead prefixes report unknown_project instead of scanning every enabled project.

A bare default:<hash> or explicit:<hash> index id is accepted as sugar for file:<id>. path exits 0 on success, 1 when the reference is malformed, missing, or ambiguous (status and candidates go to stderr), and 2 for kinds with no filesystem identity (stitch:, patch:, and historical commit: / bug:), pointing at show instead. open treats historical commit: as non-viewable, opens historical bug: in a browser, and currently opens a canonical stitch: at its resolved checkout; patch: has no viewer path. show also reports consumption from the append-only ~/.sase/artifacts/consumption.jsonl ledger: consumption_count, consumed_by_agents, consuming_agents, and last_consumed_at in pretty output, plus an additive consumption object in -j/--json output. doctor exits 1 when it finds missing enrichment fields, missing stored files, duplicate ids, unsupported schema versions, malformed rows, digest mismatches, or an unhealthy artifact-link graph. Its link report includes dangling refs, stale projections, durable-versus-aggregate counts, read-outbox counts, derived coverage, and relation/origin totals; see Artifact Links.

stats, prune, reclaim, and opt-in automatic retention use one shared protection collector. It unions IDs found in persistent ProjectSpec and SDD text with canonical fragment-free file: IDs recorded in the consumption ledger, then passes the deduplicated set to the applicable planner. Stats keeps referenced, consumed, overlap, and total protection counts distinct. Prune and reclaim are dry runs unless --apply is passed and never select explicit or protected rows; automatic retention enforces the same union after agent finalization when artifacts.retention.enabled is true. A missing consumption ledger contributes no IDs; a present ledger that cannot be queried appears in protection-source evidence and blocks destructive apply or skips automatic enforcement.

Every removal prune, reclaim, and automatic retention perform routes through the restorable trash under ~/.sase/artifacts/trash/: one directory per entry holding entry.json (the complete original index row plus trashed_at, reason, and size_bytes) and, for a byte-backed row, the moved payload file. Nothing else hard-deletes. trash restore puts the payload back and re-inserts the index row; only trash purge deletes permanently, and without -a/--all it deletes only entries older than artifacts.retention.trash_grace_days. Because trashed bytes still occupy disk, du does not drop until a purge runs; both apply summaries print the trash root, and reclaim --apply says so outright. See Store Lifecycle for the end-to-end workflow.

sase questions

Flag Values Default Description
questions_json string (required) JSON string containing questions to ask

sase agent

sase agent provides cross-project visibility into running agents and synchronizes shared agent history. Subcommands:

Subcommand Flags Description
list -a/--all, -j/--json, -p/--project List running agents. -a includes DONE/FAILED agents (capped at 50 per project). -j emits a JSON array with a stable schema. -p limits output to a single project.
search query words, -j/--json, -l/--limit, -p/--project Search the historical catalog with the Artifacts → Agent Boolean dialect. Bare search hides hidden and workflow-child rows and caps at 40; -l 0 or limit:all is unlimited. Explicit -l wins over a query limit: token.
show <name> Render a full detail panel (prompt, reply, metadata) for a single agent by name.
kill -n/--name SIGTERM a running agent by name.
wait names or -a/--all, -i/--interval, -j/--json, -p/--project, -q/--quiet, -t/--timeout, -w/--wait-blocked Wait for agents, sessions, clans, or workflows to settle; exit status distinguishes failure, human blocking, timeout, and signals.
hold create / list / release / run / show; selectors -n/--name, -t/--tribe, -H/--hood, -f/--future, -p/--pending; -s/--scope, -T/--ttl; list/show -j; release/show -k/--key Arm and manage durable admission holds that keep matching WAITING/QUEUED agents, later launches, and undispatched procs from starting. Bare hold defaults to list. See Agent holds.
restart <name>, -j/--json, -m/--model, -n/--dry-run, -y/--yes Stop one agent and relaunch its stored prompt under the same name. Planning precedes mutation; dry-run previews and JSON skips confirmation.
drain <provider>, -j/--json, -l/--limit, -m/--model, -n/--dry-run, -y/--yes Replan and relaunch agents stranded by a hard-disabled provider. Enabled and soft-disabled providers are refused.
tribe set / unset / list Manage the user-defined tribe on an agent (used by the Agents tab tribe side panels). tribe set -n <agent> -t <tribe> replaces any prior tribe; tribe unset -n <agent> clears it; tribe list [-n <agent>] prints tribes as JSON (filtered when given).
archive rebuild-index / verify Maintain the dismissed-agent bundle summary index under ~/.sase/dismissed_bundles/. verify exits non-zero if rows are stale or missing.
artifacts layout status / migrate / verify / rollback, -P/--project, -p/--projects-root, -i/--index-path, -j/--json; -m/--manifest on migrate/verify/rollback; migrate also -d/--dry-run, -l/--limit Inspect and migrate the physical ace-run artifact directory layout. status reports flat and sharded directory counts, migrate moves flat timestamp directories into day shards (with --dry-run, it only writes or prints the manifest), verify checks current or manifest-backed state, and rollback reverses a manifest-backed migration.
index status / rebuild / verify / gc / vacuum, -i/--index-path, -p/--projects-root, -j/--json; vacuum accepts -a/--apply; gc accepts --dry-run and -r/--purge-revived-bundles Maintain the persistent artifact index. status is a lightweight check, verify compares source artifacts, gc rebuilds the index and dismissed projection (--dry-run only reports its reconciliation counts, and -r first purges dismissed bundles for already-revived agents), and vacuum reports or reclaims SQLite freelist pages.
names migrate-auto, purge-local-state, -f/--force, -j/--json; purge-local-state also takes -a/--apply Maintain the permanent agent-name registry. migrate-auto runs the historical generated-name namespace migration; --force reruns it after the completion marker exists. purge-local-state removes every locally materialized import closure regardless of transport or source machine (artifacts, chats, dismissed bundles, identities, historical import staging, incoming-cache directories, and receipts); a dry run unless -a/--apply is given. -j/--json emits a machine-readable summary for any subcommand.
prompts list / migrate / show / validate, -p/--project, -m/--month, -j/--json; migrate -w/--write, validate -s/--show-warnings Inspect the canonical agents-sidecar prompt archive. list browses prompts/<YYYYMM>/; show prints the archived Markdown document; validate checks headers, artifact bytes, manifests, and plan links; and migrate moves historical plans-sidecar prompts only with --write.
sync -c/--check, -d/--drop-retired, -g/--repair-digests, -j/--json, -m/--repair-manifest, repeatable -p/--project, -q/--retry-quarantined, -r/--refresh, -t/--retry-retired Pull enabled agents sidecars, publish locally commit-eligible hoods, restore deferred prompt archives, push, and drain Referenced By write-backs. Plain --check uses cached status without Git or artifact scans; --check --refresh fetches and recomputes status. Mutating sync can retry quarantined requests, revive a project's retired agent-publication requests (--retry-retired requires --project), or drop retired hood and back-reference requests. --repair-digests re-signs drifted locally owned hood-snapshot file references and --repair-manifest rebuilds missing owner-manifest entries, each instead of a normal sync. See Agent Hood Synchronization.

Agent-index paths default to ~/.sase/agent_artifact_index.sqlite and ~/.sase/projects. sase agent index vacuum is a dry run unless -a/--apply is supplied. It reports freelist pages left by normal SQLite deletes and dismissed-identity row counts. Apply runs SQLite VACUUM to rebuild the index file and reclaim that free space; it never removes or alters an index row. -i/--index-path selects another index and -j/--json emits the machine-readable report.

sase agent persist-cleanup, sase agent persist-directive, and sase agent revert are durable-operation entrypoints that sase's TUI runs as procs; they read their details from the -Q/--request-path sidecar and are not meant for direct use.

sase agent-cli

sase agent-cli inventories the supported coding-agent CLIs, installs the ones whose provider declares an install script, and updates the ones whose install method can be identified safely. With no subcommand, it defaults to sase agent-cli list. See Agent providers for the per-install-method update behavior.

Subcommand Flags Description
list -j/--json, -o/--offline, -r/--refresh, -v/--verbose List every supported CLI with its binary, installed and latest versions, install method, and update marker. -v adds executable paths, docs URLs, and probe errors.
update <name> ..., -a/--all, -j/--json, -n/--dry-run, -o/--offline, -r/--refresh Update named CLIs or, with -a, every installed one. -n prints exact commands and skip reasons without running them. Passing neither names nor -a is a usage error.
install <name> ..., -f/--force, -j/--json, -n/--dry-run, -o/--offline, -r/--refresh, -y/--yes Install named CLIs from the install script their provider declares. Shows the URL, SHA-256 digest, exact shell-free command, env overlay, and target directory, then requires -y or an interactive confirmation. -n prints that plan and executes nothing; -f reinstalls an already-installed CLI.

-o/--offline uses only cached latest-version data and never contacts the network; -r/--refresh bypasses that cache.

sase chat

sase chat discovers and inspects saved agent chat transcripts. With no subcommand, it defaults to sase chat list. Subcommands:

Subcommand Flags Description
list -j/--json, -l/--limit, -m/--machine, -P/--provenance, -q/--query List recent transcripts with sync provenance. -j emits the stable JSON shape consumed by the /sase_chats skill.
show -n/--agent, -p/--path, -b/--basename, -f/--format Show one transcript by agent name, path, or basename. --format accepts raw, resume, or response.

Directory Sharding

Older SASE layouts wrote many agent artifacts (chat logs, notifications, workflow state, etc.) directly under ~/.sase/<kind>/. After a few months of heavy use those directories can accumulate tens of thousands of files, which slows down filesystem walks and makes ls-style inspection painful.

Current high-volume writers use a YYYYMM/ shard inside each managed artifact directory (keyed by the current month). Readers transparently merge sharded and non-sharded files, so the layout is backwards-compatible - existing unsharded files at the top level are still found and the layout is fully read/write compatible across both forms.

Prompt history uses its own monthly JSON shard directory, ~/.sase/prompt_history/YYMM.json, because each shard stores a bounded JSON list rather than one file per prompt. Entries whose last-used timestamp cannot be parsed are kept in unknown.json. The legacy ~/.sase/prompt_history.json file is migrated into that directory on first read or write when the shard directory does not already exist, then preserved as a legacy-imported-<timestamp>.json.bak backup.

sase's TUI run artifacts also support a day-sharded physical layout under each project's artifact root. Use sase agent artifacts layout status to inspect flat versus sharded ace-run directories, migrate to move legacy flat timestamp directories into shards while writing index aliases, verify to check the current or manifest-backed state, and rollback -m <manifest> to reverse a migration when needed. Migration skips live artifact directories with running.json, refuses existing targets, and can be previewed with --dry-run.

Local State Cutover

sase migrate is a temporary, offline kit for the canonical-only local-state cutover. It never runs automatically, no other SASE surface invokes it, and the whole command group is deleted once the cutover completes. Reach for it only when you are deliberately retiring legacy residue under ~/.sase and ~/.macros; nothing here is part of day-to-day operation.

Every subcommand accepts -j/--json for a machine-readable object. Bare sase migrate delegates to sase migrate list. Only backup, restore, run, and resume accept -a/--apply. For those commands, --apply is the boundary for changing the source or live root, not a promise that the default mode performs no filesystem writes: plan persists control records, restore always creates a staging copy, and path resolution may create the cutover control directories.

Where The Kit Writes

Backups, staged restores, and run journals live under the cutover backup root: $SASE_CUTOVER_BACKUP_DIR when set, otherwise ~/cutover-backups. The root is created with mode 0700 on first use. SASE rejects a root equal to or nested beneath ~/.sase, ~/.local/state/sase, or ~/sase. That check does not discover arbitrary custom runtime roots, so if SASE_HOME or a checkout lives elsewhere, choose an unrelated backup location yourself.

Path Contents
backups/<backup-id>/payload/ The copied source tree, mirroring the backed-up root.
backups/<backup-id>/ MANIFEST.json, SHA256SUMS, and provenance.json for that backup.
restores/ Staged restores, before an apply swaps one into place.
runs/<run-id>/manifest.json The planned operation, including any bound backup id.
runs/<run-id>/journal.jsonl The append-only, fsynced record a resume replays.
runs/<run-id>/run.lock The bounded lock run and resume take.
runs/<run-id>/receipt.json The final receipt written once an applied run finishes.

A backup id looks like <host>-<YYYYMMDDTHHMMSS>-<random>; a run is addressed by its run id.

The Operation Catalog

The catalog is fixed — sase migrate list reports it along with whether each declared root is present on this machine.

Operation What it does Backup required Apply supported Declared roots
import-purge Wraps sase agent names purge-local-state behind a verified backup, then re-runs the import-state preview to verify. yes yes ~/.sase/agents_sync, ~/.sase/artifacts, ~/.sase/chats, ~/.sase/dismissed_bundles, ~/.sase/projects
lock-residue Classifies code-swap lock files and refuses to archive any lock the current code still writes. no no (read-only) ~/.sase/locks
procs-residue Parses residual ~/.sase/tasks rows, reconciles them with canonical procs, and archives only a fully matched legacy tree. yes yes ~/.sase/tasks, ~/.sase/procs
state-residue Archives the legacy agent tribe file, user_question, plan_approval, and legacy ~/.macros residue once canonical counterparts exist. yes yes ~/.sase/agent_tags.json, ~/.sase/plan_approval, ~/.sase/user_question, ~/.macros

Each operation declares its own preconditions and rollback unit; list --json prints them verbatim. lock-residue reports a classification and never mutates anything, so it has no apply mode.

Backing Up And Restoring

Treat the kit's “offline” label as an operator requirement. Before capturing a backup, stop sase's TUI, AXE, agents, procs, and any other process that can write the source tree; the backup command does not stop writers or acquire a tree-wide lock. A safe sequence is:

  1. Stop writers, preview the backup, and then repeat it with --apply (preferably with --secondary).
  2. Create the operation manifest with the verified backup id.
  3. Run the manifest without --apply to preflight it.
  4. Repeat run with --apply, then use verify and status.
  5. Use resume --apply only for an interrupted applied run.
sase migrate backup ~/.sase                                  # report what would be captured
sase migrate backup ~/.sase --apply --secondary /mnt/backup  # capture, plus a second durable copy
sase migrate restore <backup-id>                             # verify, create a staging copy, and diff
sase migrate restore <backup-id> --apply                     # swap the staged copy into place

An applied capture checksums its file members and copies SQLite databases through the database backup API rather than as raw bytes. Ordinary files are copied while the tree is being walked, which is why stopping writers matters. The capture refuses to start unless free space covers the measured size with 15 percent headroom. Without --apply, it reports member counts, byte totals, and whether the destination passes the three fixed root exclusions above; resolving that destination can still create the cutover root and backups/ directory.

A restore first hashes regular and SQLite payload files against the hashes stored in MANIFEST.json. The backup also contains SHA256SUMS, but restore does not currently read that file. After verification, restore always copies the payload to a new staging path under restores/ and reports the diff and ownership deltas against the live root, even without --apply. With --apply, an existing live root is renamed to a sibling <name>.pre-restore-<YYYYMMDDTHHMMSS> — never deleted — and the staged copy is moved into its place. -r/--root targets a different live root than the one that was backed up. The backup itself is never modified or deleted by a restore.

Planning, Running, And Verifying

sase migrate plan state-residue --backup-id <backup-id>  # write runs/<run-id>/manifest.json
sase migrate run <manifest>                              # preflight only: digests, conflicts, backup record
sase migrate run <manifest> --apply                      # execute, journaling every step
sase migrate status                                      # every recorded run and its journal state
sase migrate verify <run-id>                             # re-check post-conditions and fingerprints
sase migrate resume <run-id> --apply                     # continue an interrupted run

plan mutates no source data: it writes a manifest plus an initial journal record and prints the manifest path. run without --apply checks source digests, conflicts, and the bound backup record; a refused preflight can append a refusal to the journal. With --apply, run executes under a durable journal and writes a receipt. resume replays the journal, re-checks source digests while the run has not started applying, and continues from the next resumable step. run and resume accept -l/--lock-timeout-ms; the bounded run lock is acquired only in apply mode.

plan and list accept -r/--root and -d/--home to resolve operation roots against a SASE home other than the live one, which is how the kit is rehearsed against a copy before it is pointed at real state.