XPrompt Template Reference¶
XPrompts are reusable prompt templates with optional typed inputs and Jinja2 support. They let you define a prompt
fragment once and reference it by name anywhere a prompt is composed, keeping prompts DRY and consistent across
projects. Inline prompt fragments use #name; standalone workflows use #!name when they are launched as workflows.
Use xprompts when you want to:
- Share common instructions across multiple prompts (e.g., output format rules, role definitions).
- Parameterize prompts with typed, validated arguments.
- Compose prompts from smaller building blocks using
#name(args)syntax.
There are two related paths to keep separate:
launch setup:
multi-agent xprompt fan-out check
-> default workspace ref insertion when needed (#git:home)
-> project alias canonicalization (#gh:bob -> #gh:bob-cli)
-> workspace ref resolution (#cd/#git/#gh/#hg and known-project fallbacks)
-> prompt/workflow execution
xprompt expansion inside a prompt or prompt_part:
alias substitution
-> fenced-block and disabled-region protection
-> iterative reference expansion (parse -> lookup -> args -> render -> substitute)
-> directive extraction at the launch or workflow-step boundary
The checked-in infographic prompt in docs/images/xprompt-resolution-infographic.prompt.md tracks the intended visual
version of this model; the text model above is the authoritative current reference for resolver order.
Table of Contents¶
- CLI Subcommands
- Editor LSP
- Discovery Order
- File Format
- Reference Syntax
- Arguments
- Shorthand Syntax
- Typed Inputs
- Output Specification
- Jinja2 Integration
- Legacy Placeholders
- Tags
- Snippet Field
- Skill Field
- Bundled Skills
- Built-in XPrompts
- Config-Based XPrompts
- Local Configuration Files
- Directives
- Command Substitution
- Protected Content
- XPrompt Aliases
- Recursive Expansion
- Multi-Agent Prompts
- Multi-Agent XPrompts (Library-Defined Fan-Out)
- Relationship to Workflows
CLI Subcommands¶
The sase xprompt command provides five subcommands for working with xprompts. With no subcommand, it defaults to
sase xprompt list. Flags belong to the explicit subcommand, so use forms like sase xprompt expand --trace '#plan'
rather than putting --trace on bare sase xprompt.
sase xprompt expand¶
Expands xprompt references in a prompt. Reads from a positional argument or stdin.
sase xprompt expand '#greet(Alice)' # Expand from argument
echo '#greet(Alice)' | sase xprompt expand # Expand from stdin
sase xprompt expand --trace '#plan' # Show expansion trace on stderr
The --trace flag prints a detailed expansion trace to stderr showing each resolved reference, its source file,
arguments, and expanded content. This is useful for debugging reference resolution order and understanding how a complex
prompt is assembled.
sase xprompt explain¶
Shows a dry-run visualization of a workflow's execution plan without actually running it. Displays workflow metadata, input requirements, resolved arguments, and the full step-by-step execution plan with types, control flow annotations, rendered step bodies, and output schemas.
sase xprompt explain my_workflow # Explain with no args
sase xprompt explain my_workflow arg1 arg2 # With positional args
sase xprompt explain my_workflow --arg key=value # With named args
sase xprompt list¶
Lists all available xprompts and workflows as a JSON array. Each entry includes the name, type ("xprompt" or
"workflow"), kind, reference prefix, insertion text, is_skill, source file path, user-facing input definitions,
tags, and a content preview. Clients should treat insertion as the authoritative reference text. Most xprompt and
embeddable_workflow entries insert as #name, including markdown multi-agent xprompts; standalone workflows insert as
#!name. is_skill is true only for xprompt catalog entries marked as skills; workflows report false. Step inputs
are omitted from the JSON inputs array because they are supplied by workflow execution rather than typed by a user.
sase xprompt list # JSON array to stdout
sase xprompt list | jq '.[].name' # Extract just names
sase xprompt graph¶
Generates a directed acyclic graph (DAG) visualization of a workflow. Without a workflow name, lists all available multi-step workflows with their step counts and source paths.
sase xprompt graph # List all workflows
sase xprompt graph my_workflow # Mermaid DAG (default)
sase xprompt graph my_workflow --format text # Plain-text summary
The Mermaid output can be pasted into any Mermaid-compatible renderer. Parallel sub-steps are shown as subgraphs, and nodes include type indicators and control flow annotations.
sase xprompt catalog¶
Renders every visible xprompt to a formatted PDF catalog for browsing and sharing.
sase xprompt catalog # Write the PDF to a tempdir and print its path
sase xprompt catalog --out /tmp/out # Write the PDF to the specified directory
The command collects all visible xprompt templates, renders each into an HTML section, and produces a single PDF using
the bundled catalog_template.html.j2 and catalog_style.css. The mobile/helper structured catalog uses the same
collection and classification code, but returns JSON metadata instead of requiring a PDF renderer.
Editor LSP¶
sase lsp starts the SASE xprompt language server over stdio for editor integrations. It resolves the server command in
this order:
SASE_XPROMPT_LSP_CMD, parsed as a shell-style command for development.sase-xprompt-lsponPATH.- A debug or release
sase-xprompt-lspbinary under a sibling../sase-corecheckout. cargo run --manifest-path ../sase-core/Cargo.toml -p sase_xprompt_lsp --whencargois available and the sibling checkout has aCargo.toml.
Examples:
sase lsp
sase lsp --version
SASE_XPROMPT_LSP_CMD='cargo run --manifest-path ../sase-core/Cargo.toml -p sase_xprompt_lsp --' sase lsp
Use SASE_XPROMPT_LSP_CMD for any non-default LSP command. SASE_CORE_DIR is a Justfile build/install override, not
part of sase lsp command resolution.
The LSP loads the supported xprompt catalog sources directly in Rust for completion, hover, diagnostics, and definition
requests. sase lsp exports the installed package xprompt paths to the server so built-in Markdown prompts, YAML
workflows, default config prompts, project-local prompts, user config prompts, and memory prompts do not require a
Python helper subprocess on the completion path. The Python helper bridge remains stable for mobile clients and as a
compatibility fallback for sources the Rust loader cannot discover.
When the editor advertises LSP completionItem.snippetSupport, the server also returns SASE snippets as ordinary
CompletionItemKind.Snippet entries after bare trigger words such as fix or review. Snippet entries are loaded from
the same registry as ACE: xprompts with snippet front matter plus user-defined ace.snippets, with ace.snippets
winning on trigger collisions. The editor does not need to shell out or parse SASE config to discover snippets.
The Python helper operation sase editor helper-bridge snippet-catalog is the authoritative snippet registry because it
matches ACE's xprompt composition behavior. The Rust server also has a native fallback for simple xprompt snippets and
ace.snippets so completion can degrade gracefully if the helper is unavailable. That fallback intentionally skips
xprompts that require complex Jinja or composition it cannot mirror exactly; when the helper is available, its response
is preferred.
See the editor integration guide for setup, feature coverage, helper bridge usage, and troubleshooting.
Discovery Order¶
Markdown xprompts are loaded from multiple locations. When two locations define an xprompt with the same name, the higher-priority source wins (first-wins).
| Priority | Location | Notes |
|---|---|---|
| 1 | .xprompts/ (CWD, hidden dir) |
Highest priority; project-local overrides |
| 2 | xprompts/ (CWD) |
Non-hidden variant |
| 3 | ~/.xprompts/ (home, hidden dir) |
User-wide overrides |
| 4 | ~/xprompts/ (home) |
Non-hidden variant |
| 5 | ~/.config/sase/xprompts/{project}/ |
Project-specific (when project is set) |
| 6 | sase.yml xprompts: section |
Config-based definitions (local + global) |
| 7 | Plugin packages (sase_xprompts EPs) |
Installed plugin xprompts |
| 8 | <sase_package>/default_xprompts/*.md |
Built-in default markdown xprompts |
| 9 | <sase_package>/xprompts/*.md |
Built-in package xprompts shipped with core SASE |
Each directory-based source can contain individual .md files. YAML workflows use the same CWD, home, project, plugin,
and package locations, with .yml or .yaml files loaded as workflow definitions. Within priority 6, the config merge
chain applies: built-in defaults, plugin configs, ~/.config/sase/sase.yml, overlay files (sase_*.yml), and finally a
local ./sase.yml in the current working directory (highest config priority).
For file-based xprompts (priorities 1-5 and 7-9), the xprompt name defaults to the filename stem (e.g., summarize.md
defines the xprompt summarize). The name can be overridden via the name field in the YAML front matter.
Project-specific xprompts (priority 5) are namespaced: a file bar.md in the foo project directory becomes foo/bar.
Inline-capable project xprompts are referenced as #foo/bar; standalone project workflows are referenced as
#!foo/bar.
When a project is detected (via the workspace provider), CWD xprompts (priorities 1-2) and local config xprompts are
also auto-namespaced with the {project}/ prefix. For example, if the project is myapp and xprompts/deploy.md
exists in the CWD, it becomes myapp/deploy and is referenced as #myapp/deploy. A project workflow with no
prompt_part would instead be launched as #!myapp/deploy. This prevents name collisions between project-local
xprompts and global or built-in ones.
File Format¶
An xprompt file is a Markdown file with optional YAML front matter delimited by --- lines. Everything after the
closing --- is the template body.
---
name: greet
description: Greet a named user.
input:
user_name:
type: word
description: User name to include in the greeting.
---
Hello, {{ user_name }}! Welcome aboard.
Front Matter Fields¶
| Field | Required | Description |
|---|---|---|
name |
No | XPrompt name (defaults to filename stem) |
input |
No | Input parameter definitions (see Typed Inputs) |
snippet |
No | Opt-in to ACE snippet expansion (see Snippet Field below) |
description |
No | Human-readable one-line description of what the xprompt does |
skill |
No | Marks this xprompt as an agent skill source for sase skill init (see below) |
xprompts |
No | File-local helper xprompts whose names must start with _ |
If no front matter is present, the entire file content is the template body and the filename stem is the name.
Markdown xprompt files can carry file-local helper xprompts under xprompts:. These helpers use the same structured
format as config-based xprompts, including typed inputs and descriptions, and they can reference each other
transitively. During expansion they inherit the containing xprompt's arguments and template scope, so a helper can use
values such as {{ topic }} from the outer xprompt. They are visible only while expanding the containing xprompt and
must use _-prefixed names such as _review_rules; they do not leak into the global catalog, completion catalog, or
other xprompt files. This underscore rule also applies to local xprompts in ad hoc prompt front matter, while YAML
workflow-local xprompts follow the workflow rules described in workflow_spec.md.
Reference Syntax¶
Reference inline-capable xprompts inside any prompt with the # prefix, including markdown-defined multi-agent xprompts
whose body contains top-level --- segment separators. Use #! only for standalone YAML workflows that do not have a
prompt_part step. The marker must appear at the start of the string, after whitespace, or after one of ([{"'. For
compatibility, #!name is still accepted for multi-agent xprompts, but new prompts should use #name.
| Syntax | Description |
|---|---|
#name |
Inline/template reference, no arguments |
#name(args) |
Inline parenthesis syntax with comma-separated arguments |
#name:arg |
Inline colon syntax, passes arg as a single positional arg |
#name:a,b,c |
Inline colon syntax with comma-separated multiple args |
#name:`arg with spaces` |
Colon+backtick syntax for args containing spaces (single only) |
#name+ |
Plus syntax, equivalent to #name:true |
#ns/name |
Namespaced reference (e.g., project-specific) |
#!name |
Standalone workflow reference, no args |
#!name(args) |
Standalone workflow reference with parenthesized arguments |
#!name:arg |
Standalone workflow reference with one colon-style arg |
#!name!! / #!name?? |
Standalone workflow with an explicit HITL approval override |
Examples:
sase run '#!sync'
sase run '#gh:sase #!sase/pylimit_split %approve'
During the compatibility window, top-level legacy invocations such as sase run '#sync' still run but emit a warning
that points to #!sync. Inline expansion contexts reject standalone workflows instead of passing literal #sync text
to the model. Shell examples should use single quotes around #!... so ! is not interpreted by interactive shells.
For workspace references, underscores can be used as an alternative to colons: #gh_sase is equivalent to #gh:sase.
The underscore is normalized to a colon before pattern matching, so both forms work identically. This is useful in
contexts where colons are inconvenient. The #cd directory workflow is still a workspace reference even though it skips
VCS checkout/release work.
Provider-backed references also support @name agent references in the ref portion. The @name is resolved at runtime
to the named agent's ChangeSpec (branch name), allowing one agent's prompt to target another agent's workspace:
#gh:@planner resolves to e.g. #gh:planner_add_config_parser
#gh_@reviewer same, underscore form
This is useful when chaining agents — for example, a review agent can target the branch created by a prior agent using
@name instead of hardcoding the branch name.
VCS Workspace References¶
Workspace-managing workflows use the same #name:ref reference syntax as xprompts, but they control where the agent
runs before the rest of the prompt is executed. Some workspace references are VCS-backed (#git, #gh, #hg); #cd
is directory-backed and does not reserve a numbered workspace.
| Reference | Behavior |
|---|---|
#cd:<path> |
Run in a local directory without reserving a numbered SASE workspace or doing VCS checkout/release work |
#git:<ref> |
Run in a bare-git workspace |
#gh:<ref> |
Run in a GitHub workspace, when the GitHub plugin is installed |
#hg:<ref> |
Run in a Mercurial workspace, when an hg workspace plugin is installed |
Prompts that do not contain a workspace reference are normalized to #git:home, so a bare prompt runs from the managed
bare-git home project by default and gets normal numbered workspace, checkout, diff, and release behavior. Use
#cd:~, #cd:/abs/path, #cd:relative/path, #cd:../sibling, or #cd(.) to choose a directory explicitly and skip
VCS workspace management.
By default, a missing or uninitialized home ProjectSpec is bootstrapped as a managed empty bare-git project at the
default home paths. To make bare prompts use an existing home/dotfiles bare repository, register a bare repository
whose basename resolves to home, for example #git:/path/to/home.git. Use #cd:~ when you want a direct
home-directory run without VCS workspace management.
Provider-prefixed refs that point at a known project name are preserved as workspace launches even if the matching
workspace plugin is not loaded in the current process. Known projects come from ~/.sase/projects/*/*.sase (with legacy
~/.sase/projects/*/*.gp accepted as a fallback). A launch such as #gh:sase #!fix_just therefore targets the
registered sase project, allocates a numbered workspace for non-wait runs, and lets dispatch surfaces strip the
wrapper ref when identifying an embedded workflow body.
Known projects may also declare PROJECT_ALIASES in their ProjectSpec. Alias refs in VCS workspace tags are
canonicalized before workspace resolution and xprompt expansion, so #gh:bob #p is processed as #gh:bob-cli #p when
the bob-cli project declares alias bob. The rewrite is exact and applies to colon, underscore, and parenthesized
workspace-ref forms; it does not rewrite owner/repo paths such as #gh:bbugyi200/bob, partial project names, prose, or
fenced code examples. See Project Aliases for validation and management commands.
GitHub owner/repo refs use aliases after first use. Resolving #gh:foo-org/foo creates or reuses the canonical
project whose WORKSPACE_DIR is ~/projects/github/foo-org/foo/; for a new repo that canonical name is typically
gh_foo-org__foo, with generated alias foo. A second repo with the same basename, such as #gh:bar-org/foo, gets a
different canonical project such as gh_bar-org__foo and the next available alias, for example foo-2. Future launches
can use #gh:foo and #gh:foo-2, and those refs canonicalize before prompt history, metadata, and artifacts are
written.
For compatibility, existing basename ProjectSpecs are reused when their WORKSPACE_DIR already matches the GitHub repo.
Owner/repo fallback avoids basename routing when duplicate GitHub basenames would make that ambiguous; direct
owner/repo refs match the GitHub workspace path first, then only use a basename fallback when it is unambiguous.
ACE and the xprompt LSP provide a project/ChangeSpec completion helper for these references. Type #+ at a token
boundary, or type + as the first character in the prompt, to open a picker of active launchable projects and active
PR-sized ChangeSpecs in WIP, Draft, Ready, or Mailed status. Accepting a project row inserts a tag such as
#gh:sase; accepting a ChangeSpec row inserts a tag such as #gh:my_change. The helper filters by project name,
project alias, or ChangeSpec name prefix, and it ignores system-managed home, inactive projects, sibling records, and
non-launchable projects.
Known-project lookup defaults to active ProjectSpecs. Inactive and sibling projects are omitted from broad project-local
xprompt catalogs and normal VCS workspace resolution; an explicit reference to an inactive known project fails with a
hint to run sase project activate <project> before launching new work. Management and history code paths that need
hidden projects opt into an all-state scan explicitly.
The raw colon form stops at whitespace, so paths with spaces should use the parenthesized form when possible:
#cd(/tmp/my project). Backtick quoting is supported for ordinary xprompt arguments, but workspace-reference path
matching is intentionally conservative; prefer paths without spaces for embedded workspace tags.
Double underscores (__) in xprompt names are treated as forward slashes (/), enabling flat references to namespaced
xprompts. For example, #foo__bar resolves to the xprompt registered as foo/bar, and #a__b__c resolves to a/b/c.
Single underscores are not affected. This is useful when / is inconvenient in certain input contexts (e.g., shell
completion or certain prompt editors).
Markdown headings like # Heading are not matched because a space after # prevents the pattern from firing.
Arguments¶
Positional Arguments¶
Positional arguments are comma-separated values inside parentheses:
#greet(Alice)
#format(json, 4)
Positional arguments are mapped to input definitions by position (0-indexed).
Named Arguments¶
Named arguments use key=value syntax:
#greet(user_name=Alice)
#format(style=json, indent=4)
Positional and named arguments can be mixed; positional arguments must come first:
#template(Alice, style=formal)
Quoted Strings¶
Values containing commas or special characters can be double- or single-quoted:
#note("Hello, world!", priority=high)
#tag('key=value')
Text Blocks¶
For multi-line argument values, use [[...]] delimiters:
#review([[
This is a multi-line
text block argument.
Blank lines are preserved.
]])
Text blocks automatically strip leading whitespace from the first line and dedent continuation lines by their minimum common indentation.
Shorthand Syntax¶
Shorthand syntax converts line-oriented prompt text into #name([[text]]) calls, avoiding the need for explicit text
block delimiters.
Single-Colon Shorthand¶
#name: text at the start of a line captures text until a blank line (\n\n) or end of string:
#review: Please check this code for correctness
and performance issues.
This is equivalent to #review([[Please check this code for correctness\nand performance issues.]]).
Double-Colon Shorthand¶
#name:: text captures text until the next xprompt directive at a line boundary or end of string (blank lines do not
terminate it):
#instructions:: Follow these rules:
1. Be concise
2. Be accurate
#review: Now review the code.
Paren + Shorthand¶
Combine parenthesized args with shorthand text:
#template(style=formal): Please review the following code.
#template(style=formal):: Please review the following code.
Even across blank lines (double-colon only).
The text is appended as a final positional text-block argument.
Typed Inputs¶
XPrompts can declare typed input parameters in the YAML front matter.
Longform Syntax¶
input:
- name: diff_path
type: path
description: Diff file to review.
- name: max_retries
type: int
default: 3
description: Maximum retry attempts.
Shortform Syntax¶
input:
diff_path: path
max_retries:
type: int
default: 3
description: Maximum retry attempts.
Both forms accept optional one-line description fields. Input descriptions do not change argument parsing or compact
input signatures; rich surfaces such as catalogs, explain output, argument help, and editor documentation can use them
as human-facing help text.
Supported Types¶
| Type | Aliases | Validation |
|---|---|---|
word |
-- | No whitespace allowed |
line |
-- | No newlines allowed (default type) |
text |
-- | Any content, no restrictions |
path |
-- | No whitespace |
int |
integer |
Must parse as an integer |
bool |
boolean |
Accepts true/false, yes/no, 1/0, on/off |
float |
-- | Must parse as a float |
Defaults¶
- An input with no
defaultis required. Omitting it causes a template error if the caller does not supply a value. default: nullmeans the YAML value was explicitly null. Whennullis passed as a positional or named argument value, it acts as a pass-through (the callee's own default applies).default: ""or any other value makes the input optional with that default.
Output Specification¶
XPrompts used as agent steps in workflows can declare an output schema for structured output validation. See the Output Specification section in the workflow spec for full details on the format.
Shortform Object¶
output: { name: word, description: text }
Shortform Array¶
output: [{ name: word, description: text, parent: { type: word, default: "" } }]
Longform¶
output:
type: json_schema
schema:
properties:
name: { type: word }
description: { type: text }
When an output spec is present, the agent's response is validated against the schema. Semantic types (word, line,
text, path, bool, int, float) are converted to JSON Schema types for validation and then checked for
additional constraints (e.g., word rejects whitespace).
Jinja2 Integration¶
When the template body contains Jinja2 markers ({{ }}, {% %}, or {# #}), it is rendered as a Jinja2 template.
Arguments (both positional and named) are available in the template context.
---
input: { user: word, verbose: { type: bool, default: false } }
---
Hello, {{ user }}.
{% if verbose %} Here is the detailed explanation... {% endif %}
Template Context¶
| Variable | Description |
|---|---|
{{ name }} |
Named argument or input mapped by name |
{{ _1 }} |
First positional argument (1-indexed) |
{{ _2 }} |
Second positional argument, etc. |
{{ _args }} |
List of all positional arguments |
{{ root }} |
Absolute path to the primary workspace directory (omitted if unresolvable) |
{{ wait_chats }} |
List of chat-transcript paths for agents named in %wait:<name> directives, in the order they appear |
{{ agents["build"].path }} |
Output variables loaded from %wait:build when that agent used sase var set path=... |
Named arguments and positional-to-name mappings take priority; if an xprompt is called within a workflow step, the workflow's execution scope is also available (xprompt args override scope values on conflict).
Legacy Placeholders¶
For templates that do not use Jinja2 syntax, a legacy placeholder mode is available. Placeholders use {N} syntax
(1-indexed):
Review the {1} module and check for {2:correctness}.
{1}-- required first positional argument.{2:correctness}-- second positional argument with defaultcorrectness.
Legacy mode is auto-detected: if the body contains no Jinja2 markers, legacy substitution is used.
Tags¶
XPrompts and workflows can be annotated with semantic role tags. Tags enable lookup-by-role instead of lookup-by-name,
making the system extensible — a plugin or user can override the CRS workflow simply by defining a new xprompt with
tags: crs.
Available Tags¶
| Tag | Description |
|---|---|
vcs |
Workspace workflow xprompt (#cd, #git, #gh, #hg) — wraps other embedded workflows, running setup/teardown around them |
crs |
Code Review Summary workflow (singleton — get_by_tag(crs) returns the first match) |
fix_hook |
Fix hook workflow (singleton — used by axe to find the hook-fix agent) |
rollover |
Marks workflows whose embedded references carry forward to follow-up agent steps |
mentor |
Mentor review prompt workflow |
commit |
Commit workflow (appended by mentor review A key for direct commit) |
propose |
Propose workflow (appended by mentor review a key for propose-style amend) |
make_mentor_changes |
Apply accepted mentor comments workflow (launched by mentor review Enter) |
diff_file |
Injects the CL diff into the mentor prompt |
append_to_pr |
VCS-specific post-commit prompt appended when the active commit method creates a pull request |
append_to_commit_and_propose |
VCS-specific post-commit prompt appended when the active commit method creates a commit or proposal |
create_epic_bead |
Plan-approval Epic flow — creates the plan file, beads, and the epic agent prompt |
create_legend_bead |
Plan-approval Legend flow — creates a legend-tier bead with epic_count, then runs legend work |
work_phase_bead |
Per-phase agent prompt used by sase bead work (input: bead_id) |
land_epic |
Final land-the-epic agent prompt used by sase bead work after all phases complete |
land_legend |
Final land-the-legend agent prompt used by sase bead work after legend epics complete |
Defining Tags¶
Tags can be defined in three places:
YAML workflow files (.yml):
tags: vcs, rollover # comma-separated string
# or
tags: [vcs, rollover] # list format
Markdown front matter (.md):
---
name: fix_hook
tags: fix_hook
---
Fix the failing hook...
Config-based xprompts (sase.yml):
xprompts:
my_crs:
content: "Review the code..."
tags: [crs]
Tag-Based Lookup¶
The get_by_tag() function returns the first xprompt/workflow matching a tag, respecting the standard
discovery order. This means higher-priority sources (e.g., project-local) can override built-in
tagged xprompts.
from sase.xprompt.tags import XPromptTag, get_by_tag
crs_wf = get_by_tag(XPromptTag.crs)
fh_wf = get_by_tag(XPromptTag.fix_hook)
Backward Compatibility¶
The legacy wraps_all: true field on workflows is still supported — it automatically adds the vcs tag. New workflows
should use tags: vcs instead.
Source: src/sase/xprompt/tags.py, src/sase/xprompt/models.py
Snippet Field¶
XPrompts can opt-in to ACE TUI snippet expansion by setting the snippet field in their front matter. When set, the
xprompt's content is converted into a snippet template and merged into the ACE snippet registry at startup, so users can
expand it by typing the trigger word and pressing Tab.
---
name: review
snippet: true
input:
language: word
---
Review this {{ language }} code for correctness and style.
Values:
| Value | Behavior |
|---|---|
true |
Use the xprompt's base name (part after last /) as trigger |
"custom_name" |
Use the custom string as the trigger word |
Conversion rules:
- Normal xprompt references in the content are expanded before conversion, so snippets can compose reusable xprompts
{{ input_name }}placeholders for required inputs become snippet tabstops ($1,$2, etc.){{ input_name }}placeholders for inputs with defaults are pre-filled with the default value- Legacy
{N}placeholders are also converted - XPrompts with complex Jinja2 control flow (
{% %}or{# #}) are skipped - User-defined snippets in
ace.snippetstake precedence over xprompt-derived snippets on name collision
Snippet templates can reuse other snippets with #[trigger] after the xprompt snippets and ace.snippets entries are
merged. The referenced snippet's $1, $2, ... tabstops are spliced into the caller and renumbered in document order:
ace:
snippets:
greet: "Hello $1!$0"
welcome: "#[greet] Welcome to $1.$0"
welcome expands as Hello $1! Welcome to $2.$0. Positional arguments fill the referenced tabstops before the splice:
#[greet(World)] or #[greet:World] expands as Hello World!.
Editor clients receive the same templates through sase lsp when they support LSP snippets. To troubleshoot the raw
registry, run:
printf '{"schema_version":1}\n' | sase editor helper-bridge snippet-catalog
See docs/ace.md — Snippets for snippet usage in the prompt input widget and editor completion.
Source: src/sase/xprompt/snippet_bridge.py, src/sase/xprompt/models.py
Skill Field¶
XPrompts can be marked as agent skill sources by setting the skill field in their front matter. sase skill list
shows the loaded skill catalog without writing files. sase skill init reads that catalog, including bundled skill
sources and runtime config overlays, to determine which xprompts should be rendered into per-provider SKILL.md files
and deployed to agent skill directories. By default, generated skill files begin with a
sase skill use <name> --reason ... directive so SASE can audit which skills an agent used; set log_skill_use: false
in a skill source to omit that directive (see below). Recorded skill uses can be summarized and inspected with
sase skill log. The compatibility alias sase init skills runs the same initializer.
---
name: sase_git_commit
skill: true
description: Commit changes using sase commit for git-based VCS
---
Commit instructions here...
Values:
| Value | Behavior |
|---|---|
true |
Deploy to all registered providers |
["claude", "agy"] |
Deploy only to the listed providers |
The description field provides a human-readable summary shown in sase xprompt list and sase skill list output. The
structured catalog also marks these entries with is_skill: true; ACE and editor clients use that flag to offer
slash-skill completions such as /sase_plan while keeping ordinary xprompts out of slash completion results.
The optional log_skill_use boolean field controls the generated audit directive. It defaults to true, so generated
skills instruct the agent to run sase skill use <name> --reason ... as their first step. Set log_skill_use: false to
suppress that directive for skills that should not record their own use (the bundled /sase_plan and
/sase_memory_read skills set this). The field only affects sources that are also marked as skills.
Workflow: Edit packaged skill sources in src/sase/xprompts/skills/, or define user/runtime skill xprompts through
the normal xprompt catalog sources. Do not include the sase skill use directive yourself; the generator injects it
unless log_skill_use: false is set. Then run sase skill list, sase skill init --dry-run, and finally
sase skill init --force when the preview is correct. When use_chezmoi is enabled, sase skill init commits, pushes,
and applies the generated files unless passed --no-commit, --no-push, or --no-apply. Do not edit deployed
SKILL.md files directly. sase init skills is a compatibility alias for sase skill init.
Provider plugins declare where generated skills should be written. A source can target multiple providers, and a provider can have multiple filesystem targets. Built-in targets are:
| Provider | Skill target(s) |
|---|---|
| Claude | ~/.claude/skills/<skill>/SKILL.md |
| Codex | ~/.codex/skills/<skill>/SKILL.md |
| Antigravity (agy) | ~/.gemini/antigravity-cli/skills/<skill>/SKILL.md |
| Qwen | ~/.qwen/skills/<skill>/SKILL.md |
| OpenCode | ~/.config/opencode/skills/<skill>/SKILL.md |
Bundled Skills¶
The following skills ship in src/sase/xprompts/skills/ and are deployed by sase skill init. They are packaged with
sase, included in sase xprompt list, and available to prompt completion clients even when a checkout does not have
local skill files. Coding agents invoke them as /sase_<name>. Runtime config overlays can add more skill sources, so
sase skill list may show entries that are not bundled here:
| Skill | Purpose |
|---|---|
sase_agents_status |
Report on currently-running SASE agents (list, kill, show) |
sase_artifact |
Create explicit SASE artifacts from files produced during an agent run |
sase_beads |
Reference for sase bead commands (create, update, list, ready, show, dep) |
sase_chats |
Inspect prior sase agent chat transcripts via sase chat list and sase chat show |
sase_changespecs |
Inspect and reason about ChangeSpecs via sase changespec search ..., exact-name lookup, and safe edit rules |
sase_git_commit |
Commit changes for git-based VCS via sase commit (the only sanctioned commit path on git repos) |
sase_hg_commit |
Gemini-only commit skill for the hg/fig provider path |
sase_memory_read |
Guide audited long-term memory reads through sase memory read |
sase_notify |
Inspect SASE notification inbox entries via sase notify list and sase notify show |
sase_plan |
Submit a plan file for approval (used in lieu of disabled EnterPlanMode) |
sase_questions |
Ask the user structured questions (used in lieu of disabled AskUserQuestion) |
sase_var |
Attach named output variables to the current SASE agent run |
Built-in XPrompts¶
Core xprompts ship in src/sase/default_config.yml, src/sase/default_xprompts/*.md, and src/sase/xprompts/. They
are always available without needing a project- or user-level definition. They're at the built-in end of the
discovery order, so any project, user, or config xprompt with the same name overrides the packaged
defaults. Common entries include:
| Reference | Body summary |
|---|---|
#cd |
Switch the agent into a resolved SASE workspace directory |
#git |
Check out a git ref in an isolated workspace and show resulting changes |
#commit |
Create a normal commit from completed agent changes |
#propose |
Create a proposal from completed agent changes |
#file |
Require the agent to write its response to a named markdown artifact |
#fork |
Resume context from a prior agent conversation by name |
#fork_by_chat |
Resume context from a specific chat transcript path |
#mentor |
Run a structured mentor review against a CL |
#split_file |
Ask an agent to split one large Python file into import-safe smaller files |
#summarize |
Summarize a file in a short phrase for a specified use |
#json |
Require the agent response to satisfy a JSON schema |
#!sync |
Sync the current workspace and launch conflict-resolution help if needed |
#plan |
Asks the agent to think the work through and use its /sase_plan skill before any file changes |
#epic |
Marks the request as a multi-phase epic and chains #plan |
#legend |
Marks the request as a larger legend-level planning effort that should later split into epics |
#review |
Asks the agent to fix bugs and apply only clear-win improvements |
#prompt/approve |
Boilerplate "I've edited the previous reply with my decisions; implement this" preamble + #plan |
#prompt/review |
Wraps a prompt input and asks for a gap/ambiguity review before implementation |
#research |
Asks the agent to write a new sdd/research/YYYYMM/ markdown file |
#research/image |
Asks the agent to generate an infographic for an existing research markdown file |
#research/more |
Asks the agent to improve an existing research markdown file by filling missed gaps |
#research/prompt |
Wraps a prompt input and asks for prior art, alternatives, and a recommended solution |
#research_swarm |
Fans out two independent research agents, consolidates their outputs, then generates an infographic |
#old_research_swarm |
Legacy initial, follow-up, and image research workflow (all tagged %g:research) |
#x:name,cmd |
Saves a freeform sase_xcmd command to the prompt (@$(sase_xcmd <name> <cmd>)) |
#bd/new_epic |
Multi-phase epic kickoff used by sase bead work (resolved via XPromptTag) |
#bd/new_legend |
Legend kickoff that records epic_count, commits metadata, then runs sase bead work |
#bd/work_phase_bead |
Per-phase agent prompt used by sase bead work |
#bd/land_epic |
Final land-agent prompt used by sase bead work |
#bd/next |
"What should I work on next?" helper that consults the bead tracker |
#bd/review/plan |
Plan-review helper for an epic plan |
#bd/review/prompt |
Prompt-review helper for an epic plan |
When #fork / #fork_by_chat injects a # Previous Conversation block, the prior user prompts in that block are
sanitized first: sase directives (%name, %wait, %group, ...), #/#! xprompt and workspace references, and any
unrendered Jinja2 markers ({{ }}, {% %}, {# #}) are stripped so the forked agent sees clean natural-language text.
Fenced code blocks and real markdown headings are preserved, and assistant responses are left untouched. Raw transcripts
on disk are unchanged — the cleanup happens only when building resume history (so sase chat show still shows the
original prompts).
#bd/new_epic accepts optional ChangeSpec metadata for the plan bead it creates:
#git:sase #bd/new_epic(plan_file_path=sdd/epics/202604/example.md, changespec=sase_feature, bug_id=12345)
When bug_id is supplied, changespec must also be supplied; the generated plan bead is created with the corresponding
sase bead create -c/--changespec and -b/--bug-id metadata.
#bd/new_epic also accepts an optional legend_bead_id. When supplied, or when the epic plan file frontmatter contains
legend_bead_id, the epic is linked under that legend with --type plan(<plan_file>,<legend_bead_id>) --tier epic. It
creates the epic plan bead first, then creates phase beads sequentially in the order they appear in the plan file. Phase
bead creation is intentionally not parallelized because child bead suffixes are allocated by creation order.
#bd/new_legend creates a --tier legend --epic-count <count> plan bead for sdd/legends/{YYYYMM}/..., writes
legend_bead_id, tier: legend, and epic_count frontmatter to the legend plan, commits that metadata, then runs
sase bead work <legend_bead_id> --yes.
To see the exact body of any built-in inline xprompt, run sase xprompt expand --trace '#<name>' or browse the catalog
with sase xprompt catalog. Use sase xprompt explain <name> for workflows; the explain command takes the workflow
name without a # or #! marker.
Bundled Standalone Workflows¶
The repo-level xprompts/ directory also ships standalone YAML workflows that are normally invoked with #!:
| Reference | Inputs | Purpose |
|---|---|---|
#!sase/fix_just |
none | Repair or validate just workflow issues |
#!sase/pylimit_split |
limits |
Assist with Python file-size splitting |
#!sase/refresh_docs |
project, gh_ref, threshold |
Scheduled documentation refresh |
#!sase/audit_recent_bugs |
project, gh_ref, threshold |
Scheduled audit of recently filed bugs |
#!sase/audit_recent_improvements |
project, gh_ref, threshold |
Scheduled audit of recently filed improvements |
The #!sase/fix_just workflow first bootstraps the workspace with just install, then records the results of
just fmt-check, just lint, and just test. Those three checks run before any repair step, so lint/test failures are
based on the original checkout state for that workflow run. If formatting failed, the workflow runs just fmt, stages
the resulting tree with git add -A, creates a commit when the staged diff is non-empty, then rebases on upstream and
pushes. If lint or test failed, it launches separate draft-PR repair agents through #gh:sase #pr(...) for
fix_just_linters and fix_just_tests; workflow expansion applies the normal PR context before the agents run.
The scheduled documentation refresh workflow lives in this repo as xprompts/refresh_docs.yml and is invoked as
#!sase/refresh_docs. It accepts project, gh_ref, and threshold, defaulting to the main sase repo behavior
(project=sase, gh_ref=sase, threshold=50). For scheduled checks in linked repos, pass repo-specific values such as
#!sase/refresh_docs(project=sase-core, gh_ref=sase-org/sase-core, threshold=25). The project input selects the
marker path under ~/.sase/projects/<project>/refresh_docs_marker; gh_ref is forwarded to the nested docs agent as
#gh:<gh_ref>. Scheduled lumberjack agents in sase_athena.yml reach these workflows by embedding #gh:sase-org/sase
in their prompts so the sase project workspace is selected before resolution.
Config-Based XPrompts¶
XPrompts can be defined inline in sase.yml under the xprompts: key.
Simple Format¶
xprompts:
propose: "Please propose your changes before applying them."
Structured Format¶
xprompts:
greet:
description: Greet a user a configurable number of times.
input:
name:
type: word
description: Name to greet.
count:
type: int
default: 1
description: Number of greetings to render.
content: "Hello {{ name }}, count is {{ count }}"
Config-based xprompts have priority 6 (below file-backed project and user xprompts; above plugin and built-in definitions).
Standalone workflows must be defined as YAML files in an xprompts/ directory (repo-level, project plugin, or built-in
package). Top-level workflows: blocks in sase.yml or sase_*.yml overlays are no longer supported and will be
ignored by the runtime; move any such definitions into xprompts/<name>.yml files.
Local Configuration Files¶
You can define project-specific xprompts in a sase.yml file in the current working directory. This is a full sase
config file that can override any configuration, including xprompts. It is the highest-priority config source in the
deep-merge system, overriding global sase.yml, overlay files, plugin configs,
and built-in defaults. Individual .md files in xprompts directories still take precedence over config-defined
xprompts.
xprompts:
# Simple format — value is the template body
propose: "Please propose your changes before applying them."
# Structured format — with typed inputs and/or output
greet:
description: Greet a user a configurable number of times.
input:
name:
type: word
description: Name to greet.
count:
type: int
default: 1
description: Number of greetings to render.
content: "Hello {{ name }}, count is {{ count }}"
Directives¶
Directives are in-prompt tags with a % prefix that modify agent runner behavior. They are extracted and stripped from
the prompt before further processing.
Supported Directives¶
| Directive | Alias | Description |
|---|---|---|
%model |
%m |
Override the LLM model for this prompt |
%name |
%n |
Assign, auto-generate, or force-reuse an agent name |
%wait |
%w |
Wait for another agent or workflow to succeed |
%time |
%t |
Defer launch by a duration or until an absolute wall-clock time |
%hide |
%h |
Hide the agent from the default Agents tab display |
%approve |
%a |
Run the agent fully autonomously (skip approval) |
%epic |
Enable plan mode and auto-approve the plan as an epic | |
%edit |
%e |
Return editor text to the prompt bar for review |
%repeat |
%r |
Run the prompt multiple times (e.g., %repeat:3) |
%group |
%g |
Assign the agent's user-managed tag (e.g., %group:review) |
%alt |
%{} |
Split prompt into variants with different text (brace shorthand) |
Syntax¶
Directives use the same argument syntax as xprompt references:
%model:claude-sonnet # Colon syntax
%model(claude-sonnet) # Parenthesis syntax (single value only)
%model:`claude-sonnet-4` # Backtick syntax (for values with special chars)
%model:codex/o3 # Provider/model syntax — switches both provider and model
%model:agy/flash35h # Provider/model syntax for Antigravity (agy)
%model:opencode/anthropic/claude-sonnet-4-5 # Nested provider/model syntax
%name:reviewer # Short-form
%n:reviewer # Same, using alias
%name # Bare — auto-generates a unique name
%name:!reviewer # Force reuse by wiping the previous owner
%wait:agent1 # Wait for agent1
%w:agent2 # Wait for agent2 (alias)
%wait # Bare — waits for the most recently named agent
%wait:agent1,agent2 # Multi-value: equivalent to two separate %wait: lines
%wait(agent1, agent2) # Same, paren form
%time:5m # Wait for 5 minutes before starting
%t:1h30m # Wait for 1 hour 30 minutes (alias)
%time:90s # Wait for 90 seconds
%time:1430 # Wait until 14:30 today (wraps to tomorrow if past)
%time:260415/0900 # Wait until 2026-04-15 at 09:00
%wait:agent1 %time:5m # Wait for agent1, then a 5-minute floor
%repeat:3 # Run the prompt 3 times
%r:5 # Same, using alias
%{#review | #test} # Brace shorthand: branches split on top-level `|`
%alt(#review,#test) # Long form: same two variants, comma-separated
%(#review,#test) # Legacy shorthand, still accepted (prefer `%{...}`)
%{sec=#review | perf=#test} # Named branches become child name suffixes
%{extra instructions} # Single branch: split into with/without variants
%approve # Run fully autonomously
%a # Same, using alias
%edit # Return editor text to prompt bar
%e # Same, using alias
%epic # Enable plan mode and auto-approve the plan as an epic
%group:review # Assign the tag "review" to this agent
%g:review # Same, using alias
The %model directive also supports automatic provider resolution: known model names (e.g., opus, o3,
qwen3.6-plus) are automatically mapped to their provider. See
Per-Prompt Provider Switching for the full model-to-provider mapping.
The %name and %wait directives can be used without arguments. Bare %name auto-generates a permanent unique name
for the agent. Bare %wait resolves to the most recently named agent (raises an error if no previous agent exists).
Agent-name templates contain exactly one @ marker. The marker is not a wildcard; SASE replaces it with the next token
from the shared auto-name sequence (0, 1, ..., 9, a, ..., z, 00, ...). For example, with no reserved names,
%name:@.cld renders as 0.cld, %name:build-@ renders as build-0, and %name:research.@.final renders as
research.0.final. The older terminal -@ form still works, but new allocations now start at token 0 and use the
alphanumeric sequence instead of positive integers. Later %wait, #fork, and #resume references can use the same
template text; in one multi-agent launch, SASE rewrites those references to the concrete name already planned for that
template before spawning dependent agents.
Agent names are permanent IDs. A name that belongs to any existing agent state cannot be reused by a normal
%name:<name> launch; SASE cancels the launch before spawning an agent, records the prompt as cancelled, and suggests
the lowest free numeric suffix such as <name>1. To deliberately reuse a name, use %name:!<name> from the TUI; the
! form is the explicit confirmation to wipe the previous owner and its persisted system state before launching the new
agent with that name. Non-TUI launch surfaces reject %name:!<name> unless they provide an explicit confirmation path.
Named %wait dependencies unblock only after the newest matching agent run has a done.json outcome of "completed".
For a multi-agent workflow name, the workflow root and every child agent for that root must complete successfully.
Failed, killed, crashed, still-running, malformed, or missing done.json artifacts do not satisfy the wait; the
dependent agent stays parked until a later successful run of the same dependency name appears.
When a launch has exactly one explicit %wait:<name> dependency and no explicit %name, SASE can allocate a derived
name before spawning the waiting agent: <name>.w1, <name>.w2, and so on, using the first free slot. Multi-value
waits, bare %wait, and prompts whose name depends on unresolved xprompt expansion do not get a parent-side derived
name. Repeat launches reuse this rule, then chain later repeat slots with %wait:<previous-slot-name>.
If a prompt includes both #fork/#resume and %wait, the fork-derived .f<N> name takes precedence over the
wait-derived .w<N> name. The wait still controls launch ordering, but the planned agent name follows the resume/fork
lineage.
The %time directive (alias %t) defers launch by a duration or until an absolute wall-clock time:
- Durations in
XhYmZsformat (e.g.,%time:5m,%time:1h30m,%time:90s). When multiple%timedurations are specified, the maximum is used. HHMM— wait until that time today (e.g.,%time:1430for 14:30). If the time has already passed, it wraps to tomorrow.yymmdd/HHMM— wait until a specific date and time (e.g.,%time:260415/0900for 2026-04-15 at 09:00). Raises an error if the target is in the past.
%time and %wait combine freely: dependencies wait first, then the time floor applies.
Absolute time waits cannot be combined with duration waits or with each other.
Bare %time is invalid — %time requires a duration or absolute time argument. Time-shaped values passed to %wait
(e.g. %wait:5m) raise an error with a migration hint pointing to %time:5m.
Multi-value directives (%wait, %time, %model, %alt) accept comma-separated arguments to collapse what would
otherwise be several lines: %wait:agent_a,agent_b is equivalent to two separate %wait: directives. Backtick-quoted
values (e.g. %wait:`a,b`) are treated as a single literal and not split on commas.
The %approve, %edit, and %epic directives are boolean flags — they take no arguments and are simply present or
absent.
Example¶
%model:`claude-sonnet-4-20250514`
%name:code-reviewer
%wait:planner
Review the code changes and provide feedback.
The directives are stripped from the prompt text. The agent will use the specified model, be named "code-reviewer", and will wait for the "planner" agent to complete successfully before running.
Hide Directive¶
The %hide directive marks an agent as hidden. Hidden agents are not shown in the Agents tab by default — press . to
toggle their visibility. This is useful for background agents spawned by axe or workflows that don't need active
monitoring:
%hide
%name:background-checker
Run periodic health checks.
Approve Directive¶
The %approve directive marks an agent as fully autonomous. The agent runs without requiring human approval for plan
steps or other checkpoints that would normally pause for user input:
%approve
%name:auto-fixer
Fix the lint errors in the codebase.
Edit Directive¶
The %edit directive causes the editor text to be loaded into the ACE prompt input bar instead of being submitted
directly. This lets you compose a prompt in $EDITOR (via Ctrl+G) and then review or tweak it in the prompt bar
before launching an agent:
%edit
Refactor the parser module to use dataclasses.
When the editor closes, the %edit directive is stripped and the remaining text appears in the prompt input bar for
further editing. The agent is not launched until you press Enter in the prompt bar.
The returned text is loaded with editor-file semantics: if it contains real multi-agent --- segment separators
(outside fenced blocks and outside leading YAML frontmatter), it is returned to the ACE prompt stack as one editable
prompt pane per agent segment, and any leading xprompt frontmatter is lifted into the prompt properties panel above the
top pane. A buffer with no separators still returns to the bar for review; if it has leading frontmatter, that
frontmatter is lifted into the properties panel rather than left as literal pane text.
Plan Approval and Coder Follow-up¶
SASE's planning workflow is driven by the /sase_plan skill together with the sase plan approval pipeline. An agent
drafts a plan and submits it with /sase_plan (or sase plan propose); the plan then pauses for user approval before
any execution. In the TUI, the agent shows a PLAN status after submitting the plan for review, then PLAN APPROVED once
the user approves it. The %epic directive opts a planning agent into this same pipeline with automatic epic approval
(see Epic Directive).
Once the plan is approved, sase launches a follow-up coder agent using the same handoff body as the #coder
built-in xprompt (see sase/xprompts/coder.md).
#coder takes the approved plan file as its plan_file input, injects it with @, and instructs the agent to
implement the plan. By default the coder does not inherit the planner's chat transcript — the plan file is the
hand-off artifact. Set SASE_CODER_INHERIT_PLANNER_CHAT=1 to restore the old behavior, in which case a
#fork:<planner_name> reference is prepended to the coder prompt so it resumes the planner's session. The coder prompt
also carries a %model: directive. A model chosen at approval time wins. When no model is chosen, the follow-up default
is resolved at handoff time from the planner agent's concrete provider/model: an active worker override, a matching
llm_provider.worker_models entry for the planner's primary lane, then the planner's own provider/model as the
fallback. The generated prefix is a concrete %model:<provider>/<model> so the planner's primary context is preserved
even if the global worker lane changes before launch (when the planner is missing provider/model metadata the follow-up
falls back to a bare %model:worker). Ordinary %model:worker directives written elsewhere still resolve from the
current effective worker lane.
Outside the TUI, sase plan shows the same pending PlanApproval notifications plus recent approved and inferred
rejected plans. Use sase plan approve <id-prefix> --kind approve|commit|epic|legend|tale to approve from a shell. The
approve kind runs the coder without committing an SDD plan; tale commits an SDD tale and runs the coder; epic and
legend commit the matching SDD tier and launch the bead follow-up; commit records the approved plan in SDD without
launching a coder. -m/--model picks the follow-up agent's model, while -p/--prompt adds extra coder instructions for
the approve and tale paths.
Epic Directive¶
The %epic directive enables plan mode and marks the submitted plan for epic approval. When the agent later submits a
plan with /sase_plan or sase plan propose, sase follows the same epic path as the TUI Epic action: it writes the SDD
epic files, commits them as needed, initializes beads, and launches the epic follow-up agent. Unlike %approve, %epic
is plan-specific and does not automatically answer unrelated questions.
%epic
%name:billing-epic
Plan the billing dashboard epic.
Repeat Directive¶
The %repeat directive runs the same prompt multiple times. The argument is a positive integer specifying the repeat
count:
%repeat:3
%name:linter
Run lint checks on the codebase.
This launches 3 independent agents — each spawned with its own process, workspace, and agent_meta.json, appearing as
its own top-level entry in the Agents tab. Fan-out happens at launch time: the directive is consumed when the agents are
spawned, so there is no outer loop or TUI affordance ticking through iterations. The slot numbers are appended to the
%name base (linter.1, linter.2, linter.3); when %name is omitted the auto-assigned base is used (e.g. a.1,
a.2, a.3).
Iterations run sequentially: all N agents are spawned up front and register immediately in the TUI, but iteration
k+1 is automatically wait-chained behind iteration k via an injected %wait:<prev_name> directive. This turns
%repeat into a serial iteration primitive — each iteration can observe its predecessor's work — without blocking the
launcher on any single agent.
Each iteration exposes two iteration-scoped named arguments in the agent's workflow:
| Variable | Meaning | Example with %repeat:5 |
|---|---|---|
n |
Current iteration (1-based) | 1, 2, 3, 4, 5 |
N |
Total iterations (the %repeat argument) |
5 |
These are threaded through via the SASE_REPEAT_ITERATION and SASE_REPEAT_TOTAL environment variables — the agent
runner reads them, converts to ints, and passes them as named args into the workflow so they appear as Jinja2 variables
in the prompt body:
%repeat:5
Run test suite batch {{ n }} of {{ N }}.
Stopping a repeat chain early with STOP¶
A repeat iteration can stop every later slot by setting the reserved STOP output variable before it completes:
%repeat:5
Process the next batch; if there is no more work, run: sase var set STOP=1
Because the slots are already spawned and wait-chained, "stopping" works on wake: when a later slot's %wait on its
repeat predecessor resolves, the slot checks that predecessor's STOP output variable. If it is truthy, the slot
propagates STOP, finalizes as a successful completed (skipped) slot — recording repeat_stopped: true and
stopped_by in its done.json — and exits without claiming a workspace or running its prompt. Keeping the outcome
completed lets the stop cascade down the chain through the ordinary %wait resolution, so each remaining slot winds
down one wait-check cycle after the previous one. STOP is conservative: "", 0, false, no, and off
(case-insensitive) are not-stop; any other value stops the chain. See
Cross-Agent Output Variables for how STOP behaves outside repeat chains.
Alt Directive¶
The %alt directive splits a single prompt into multiple variant prompts, each launched as a separate agent. Each
branch replaces the directive span in the output prompt.
The preferred shorthand is %{A | B | ...}, which uses braces and splits branches on top-level | separators:
%{#review | #test | #docs}
Analyze the codebase.
This produces three agents, each with "Analyze the codebase." but with #review, #test, or #docs substituted in
place of the directive. Branches can be arbitrary text — xprompt references, directives, plain instructions, or
[[text blocks]]. Because branches split only on a top-level |, a comma is ordinary branch text: %{foo, bar | baz}
is two branches (foo, bar and baz), not three. Nested (), [], {}, and backtick-quoted spans are not split, and
any | inside them is treated literally.
The long form %alt(...) and the legacy %(...) shorthand remain accepted; both use parentheses with comma-separated
branches:
%alt(#review, #test, #docs)
%(#review, #test, #docs)
New prompts, completions, snippets, and docs should prefer %{...}. %(...) stays parse-compatible during the
migration; it may be removed in a future release.
Named Branches¶
Branches can be named with id=value. The value is inserted into the spawned prompt and the id becomes the child
agent suffix. For example, %name:review %{sec=[[security]] | perf=[[performance]]} launches review.sec and
review.perf. Unnamed branches use numeric suffixes while skipping any numeric ids already provided by named branches,
so %{2=[[named]] | [[first]] | [[second]]} launches suffixes 2, 1, and 3.
When the same named branch id appears in more than one alt directive, those directives are correlated: values with the same id render into the same child prompt, and a missing id in one correlated directive renders as empty text. Empty renders also collapse adjacent horizontal whitespace, so they do not leave doubled spaces or spaces before punctuation; only spaces and tabs are collapsed, newlines and indentation are preserved, and non-empty branches are untouched. For example:
%name:repo %{a=Describe | b=Explain} how this repo works %{a=in detail}.
This launches repo.a with "Describe how this repo works in detail." and repo.b with "Explain how this repo works.".
Single Branch (With/Without Split)¶
A single-branch alt is treated as a with/without split — it produces two prompts: one with the branch text and one with the directive removed entirely:
%{Also check for security issues.}
Review this module.
This launches two agents: one with "Also check for security issues. Review this module." and one with just "Review this module."
Cartesian Product¶
Multiple alt directives can appear in the same prompt. Branch lists with no repeated named ids form a Cartesian product: one agent is launched per combination. Brace and paren forms mix freely:
%{Focus on security | Focus on perf} %{%m:opus | %m:sonnet}
Review this code.
This produces 2 × 2 = 4 agents (every focus area paired with every model). Model directives used as branches inside
%{...} participate in the Cartesian product naturally: %{#review | %model:opus} fans out a default-model review
branch and an opus branch.
Repeated named ids are the exception to the Cartesian rule. Disjoint named ids and unnamed branches remain Cartesian; only the same explicit id repeated across directives is zipped together.
Multi-Model Fan-Out¶
The %model directive is single-value. To launch multiple agents in parallel — one per model — put one model directive
in each %{...} branch:
%{%m:opus | %m:sonnet}
Review this code for edge cases.
This launches two agents with identical prompts, each using a different model. Each agent appears as a separate entry in
the Agents tab. Comma/paren multi-argument syntax (%m(opus,sonnet)) and repeated top-level %model directives are no
longer supported; use %{%m:opus | %m:sonnet} instead. Colon syntax (%m:opus) and single-model parentheses
(%m(opus)) launch a single agent.
When a prompt fans out to multiple models, the spawned agents share a single base name and carry a runtime suffix so
they can be told apart at a glance. Given %{%m:opus | %m:gpt-5.5} %n:foo, the two agents are named foo.cld and
foo.cdx. The runtime suffix is a short alias declared by the provider plugin (via the llm_provider_short_name hook)
— cld, cdx, agy, qwn, opc for the built-in providers — falling back to the full provider name for plugins that
don't declare one. If %name is omitted, a single auto-generated base is allocated and shared (e.g. a.cld / a.cdx)
rather than each agent picking its own letter independently. Single-model prompts retain their plain %name value
unchanged. When two models share a runtime (e.g. %{%m:opus | %m:sonnet} — both claude), the model name disambiguates
the suffix: foo.cld-opus and foo.cld-sonnet. Long model names (e.g. Gemini 3.5 Flash (High)) are replaced with a
short alias (flash35h) declared by the provider plugin, so a same-runtime agy fan-out reads as foo.agy-flash35h /
foo.agy-flash35l rather than echoing the full model string. Model arguments used for naming are first resolved through
xprompt shorthand expansion, while the launched prompt keeps the original %model value. For example,
%n:ag %{%m:#flash | %m:#pro} can launch agents named ag.agy-flash35h and ag.agy-pro31h.
Multi-Value Directives¶
The %wait directive supports multiple occurrences — each adds to the wait list:
%wait:agent1
%wait:agent2
%wait:agent3
Do work after all three agents finish.
Agent dependencies and time floors can be mixed freely:
%wait:agent1
%time:5m
Wait for agent1 to finish, then wait at least 5 minutes from launch.
Command Substitution¶
XPrompt arguments support shell command substitution using $(cmd) syntax. The command is executed via the shell and
its output replaces the $(cmd) expression.
#bug:$(branch_bug) # Use output of branch_bug command as the argument
#review:$(git diff HEAD~1) # Pass git diff output as argument
Nested parentheses are supported: $(echo $(date)). To include a literal $(, escape it as \$(.
Failed commands or commands producing empty output result in an empty string replacement. Command outputs are cached within a single expansion pass to avoid redundant execution.
Protected Content¶
Fenced Code Blocks¶
Content inside triple-backtick fenced code blocks is automatically protected from xprompt expansion:
Here's an example:
```
#foo will NOT be expanded inside this code block
```
But #foo HERE will be expanded normally.
This prevents accidental expansion of #name patterns in code examples, documentation, and similar content.
Disabled Regions¶
You can explicitly disable xprompt expansion for a region of text using the %xprompts_enabled directive:
%xprompts_enabled:false
This content is passed through verbatim.
#foo will NOT be expanded here.
%xprompts_enabled:true
Normal expansion resumes here.
#foo WILL be expanded.
The markers are stripped from the final output. This is useful for embedding raw xprompt syntax in documentation or for
passing literal #name patterns to downstream consumers.
The closing %xprompts_enabled:true marker may appear either on its own line or inline at the end of a content
line. In both forms the marker (and any whitespace immediately preceding an inline marker) is stripped from the final
output, so prompts authored as natural prose can re-enable expansion mid-line:
%xprompts_enabled:false
... raw content where #foo and @bar are passed through verbatim. %xprompts_enabled:true
And expansion resumes here.
XPrompt Aliases¶
XPrompt aliases provide raw text-level substitution that runs before any other xprompt processing. They are defined in
the xprompt_aliases config field in sase.yml.
These are global shorthand aliases for xprompt names and raw refs. They are separate from ProjectSpec PROJECT_ALIASES,
which map alternate project names such as bob to canonical known projects such as bob-cli at the launch boundary.
Project aliases are canonicalized before xprompt expansion so launch artifacts and history store the canonical project
name.
The built-in defaults provide two shorthand aliases:
| Alias | Target | Usage |
|---|---|---|
c |
commit |
#c → #commit |
p |
propose |
#p → #propose |
Additional aliases can be added in user config files:
xprompt_aliases:
gh_sase: "gh:sase" # #gh_sase → #gh:sase
gh_foo: "gh:foo/bar" # #gh_foo → #gh:foo/bar
When the processor encounters #alias_name in a prompt, it replaces the alias name portion with the target string
before any xprompt resolution occurs. This is particularly useful when the target contains characters (like :) that
must be present in the raw text for other processing logic — such as VCS directory-switching — to work correctly.
See Configuration Reference: xprompt_aliases for the full field specification.
Recursive Expansion¶
XPrompt bodies can reference other xprompts. Expansion is iterative: after each round of substitution, the result is
scanned again for new #name references. This continues until no known references remain, up to a maximum of 100
iterations (to guard against circular references).
Multi-Agent Prompts¶
A single prompt can launch multiple agents by using YAML frontmatter and --- segment separators. SASE plans the
segments in document order, but agents do not wait for earlier segments unless you add a dependency such as
%wait:<name> or bare %wait. The same ----separator convention also applies inside an xprompt body — see
Multi-Agent XPrompts (Library-Defined Fan-Out) below.
Frontmatter Panel (ACE TUI)¶
In the sase ace prompt input, ad hoc prompt frontmatter has a structured Frontmatter Panel above the prompt stack,
with the same field set an xprompt .md file supports (name, description, tags, input, xprompts, skill,
snippet). Open or focus it with the prompt NORMAL-mode g= keymap; while the panel owns focus, g= runs the panel's
deactivate/apply path. The panel also auto-shows when ACE has lifted leading frontmatter into the stack, such as a
multi-agent prompt load or an editor-file return from %edit / whole-stack Ctrl+G. A single prompt recalled from
history with leading frontmatter but no segment separator stays one verbatim pane instead of auto-opening the panel.
Typing --- in the prompt body is passive during live editing: at the very start it stays literal text, and after
content it does not split the active pane. Add a top-level property with a (a picker sourced from the same core schema
that backs the editor LSP), edit scalar/list fields inline, delete a field with d, and use R for a live-validated
raw-YAML escape hatch. The panel owns the canonical YAML it serializes back onto the prompt, so the multi-agent launch
path is unchanged.
The structured input and xprompts fields render as foldable sub-trees (h/l): navigate into them with j/k,
then A/e/d (or enter) add, edit, and delete individual items through small typed sub-form modals. The input
editor offers a name, a core-validated type (with its one-line rule shown inline), an optional default (blank
means required), and a description; the xprompts editor offers a _-prefixed name (validated by the same underscore
rule the launch path enforces), content, a compact name:type[=default] inputs field, and a description. A
#_helper declared here lights up <ctrl+t>/<ctrl+l> completion and argument hints in every prompt pane exactly like
a global xprompt — define a helper in the panel and it is instantly usable below.
Frontmatter-Defined Local XPrompts¶
YAML frontmatter at the start of a prompt can define local xprompts under the xprompts: key. These are defined once in
the frontmatter and each segment receives only the local xprompts it actually references (including transitive
dependencies). Local xprompt names must start with _ to distinguish them from global xprompts.
---
xprompts:
_review_rules: "Always check for error handling and edge cases."
---
#_review_rules
Review the authentication module.
Local xprompts support the same structured format as config-based xprompts (typed inputs, Jinja2 content):
---
xprompts:
_template:
input: { target: word }
content: "Review the {{ target }} module."
---
#_template(auth)
Frontmatter-Declared Inputs¶
Prompt frontmatter can also declare input: arguments using the same typed shorthand as xprompt files (see
Typed Inputs). The declared values are substituted into every segment's {{ name }} placeholders
before the agents fan out:
---
input:
service: word
retries: { type: int, description: how many times to retry }
dry_run: { type: bool, default: false }
---
Refactor the {{ service }} module ({{ retries }} retries, dry_run={{ dry_run }}).
When a prompt with required (default-less) inputs is submitted in sase ace, an Input Collection Modal opens after
the whole-stack submit: each required input gets a typed, validated field (optional inputs stay collapsed behind a
reveal toggle, showing their defaults), and the agents launch only once every value is valid. Non-interactive CLI
launches (sase run) cannot prompt, so a required input without a default fails fast with a clear message instead of a
cryptic template error — give such inputs a default or launch from the TUI.
Segment Separators¶
After the frontmatter block is consumed, subsequent --- lines on their own act as segment separators. Each segment
launches as a separate agent:
---
xprompts:
_common: "Follow the project coding conventions."
---
%name:step1
#_common
Implement the new feature.
---
%name:step2
%wait:step1
#_common
Write tests for the new feature.
This launches two agents. step2 starts after step1 succeeds because the second segment includes %wait:step1; if
that line were omitted, both agents would be eligible to run independently. Both agents share the _common local
xprompt.
Cross-Agent Output Variables¶
Agents can publish small string values for later waited agents or segments with sase var set KEY=VALUE. Give the
producer a stable name and make the consumer wait before referencing the producer's variables. Every producer's
variables live under a single reserved agents dictionary keyed by agent name:
%name:build-@
Build the report, then run:
sase var set report_path=dist/report.md status=ok
---
%name:review
%wait:build-@
Review {{ agents["build"].report_path }} after the build status is {{ agents["build"].status }}.
The review prompt is rendered after the build-@ dependency completes, so {{ agents["build"].report_path }} and
{{ agents["build"].status }} come from the producer's stored agent_meta.json values. A consumer that has already
started will not see later writes.
The agents key is a stable Jinja namespace for the producer, not always the producer's concrete runtime name.
Agent-name templates use the template base, so a producer that launches as build-0 from %name:build-@ is read as
{{ agents["build"].report_path }}, not agents["build-0"]. The key is otherwise the raw agent name with no identifier
munging, so dotted, hyphenated, and digit-leading names all work via bracket access: %name:research.@.final →
{{ agents["research.final"].report_path }}, and %name:0n.cld → {{ agents["0n.cld"].report_path }}. Identifier-safe
keys also support attribute access such as {{ agents.build.report_path }}. agents is a reserved agent-run Jinja
name; a workflow input named agents collides and fails clearly. Output variables are persisted in the producer's
agent_meta.json and also appear in ACE's Agents-tab OUTPUT VARIABLES metadata section. They are visible metadata,
not secret storage.
STOP is a reserved output-variable name, but only for %repeat / %r chain continuation: setting it stops later
repeat slots (see Stopping a repeat chain early with STOP). It has no
special meaning for ordinary %wait consumers, --- segments, or %alt fan-outs — those read it like any other
variable, e.g. {{ agents["name"].STOP }}.
ACE renders loaded literal --- multi-agent prompts as a prompt stack: each top-level segment becomes an editable pane,
while prompt-level frontmatter and fenced-code separators keep the same parsing rules described below. A #name
multi-agent xprompt invocation remains a single pane until launch. During live editing, typed --- lines are ordinary
prompt text; add panes explicitly from the prompt-stack controls. Stash restore and marked-agent kill-and-edit can also
seed multiple panes, but those paths preserve each selected draft or agent prompt as one pane. Use Enter to choose how
to submit stacked panes, g<enter> to launch the selected pane directly, or Ctrl+S to submit the panes together in
top-to-bottom order. See the ACE prompt-stack guide for the editing keybindings and the default
active-pane behavior.
Rules¶
- The first
---pair at the start of the document is treated as YAML frontmatter. - After frontmatter is consumed, all subsequent
---lines are segment separators. - If there is no frontmatter, ALL
---lines are segment separators. - A prompt with frontmatter but only one segment is a single-agent prompt with local xprompts (not multi-agent).
---inside fenced code blocks is not treated as a separator.- When a multi-agent prompt is saved to prompt history, each individual segment is also saved as a separate entry. This allows segments to appear independently in the prompt history picker for reuse.
Multi-Agent XPrompts (Library-Defined Fan-Out)¶
An xprompt itself can be a "multi-agent xprompt": its body contains --- separators (outside fenced blocks), and
referencing it as the sole content of a user-prompt segment fans the call out into one agent per body segment. The
spawned agents share the same input arguments — each segment is rendered with the same (args) substituted in. The
catalog, TUI picker, and completion UI display markdown-defined fan-out xprompts with the inline marker (#name). The
older #!name form is still recognized for multi-agent xprompts for compatibility, but new prompts should use #name.
# xprompts/three_phase.md
---
input:
target: word
---
%name:plan
Draft a plan for {{ target }}.
---
%name:code
%wait:plan
Implement {{ target }} following the plan.
---
%name:review
%wait:code
Review the {{ target }} implementation and propose follow-ups.
Invoking it:
sase run '#three_phase(login)'
…dispatches three agents (plan, code, review), each receiving target=login. The %wait directives chain them
sequentially; without %wait they would run in parallel.
Detection happens at dispatch time (after standard parse_multi_prompt), in src/sase/agent/multi_agent_xprompt.py,
and applies at every dispatch site (sase run, the TUI agent launcher, the query handler).
Multi-agent xprompts can also be embedded inside a larger prompt. In that case, the first rendered body segment is embedded at the reference location and the remaining rendered body segments become follow-up agent prompts:
sase run '#gh:sase Review this first: #three_phase(login)'
When the call site starts with a VCS workspace reference such as #gh:sase, #git:feature, #hg:branch, or a
known-project underscore form such as #gh_sase, that workspace reference is inherited by every generated follow-up
segment unless the generated segment already declares its own VCS reference. Leading launch directives stay before the
inherited workspace reference, so a prompt like %name:abq #gh:sase #three_phase(login) keeps %name:abq attached to
the first generated segment and prefixes #gh:sase onto follow-ups.
Rules and Limitations¶
- A sole multi-agent reference replaces the whole segment with its generated segments. An embedded multi-agent reference replaces only that reference with the first generated segment, then appends the remaining generated segments as follow-ups.
- A user-prompt segment can contain multiple multi-agent xprompt references. They expand fully in document order. Text before the first reference attaches to the first generated segment only; text between references and after the last reference is discarded.
- Ordinary inline xprompt references inside a multi-agent xprompt body remain inline xprompt references; the agent runner expands them later as normal prompt text.
---inside fenced code blocks in the xprompt body is not treated as a separator.- Recursive fan-out (a multi-agent xprompt body whose own segments reference more multi-agent xprompts) is bounded by a depth cap and will raise if exceeded.
Relationship to Workflows¶
XPrompts and workflows share the same argument grammar, but the marker communicates how the reference is allowed to participate in a prompt:
#name(args)expands inline-capable xprompts and workflows with aprompt_partstep, including markdown-defined multi-agent xprompts that fan out into multiple prompt segments.#!name(args)launches standalone YAML workflows that have noprompt_partstep.
Simple markdown xprompts are converted internally to single-step workflows with a prompt_part step, so they remain
inline-capable and continue to use #name, even when their body contains top-level --- segment separators.
YAML workflow files can set a top-level description and use the same input-description forms as markdown or
config-defined xprompts:
description: Refresh generated docs and report drift.
input:
docs_dir:
type: path
description: Documentation root to refresh.
steps:
- name: refresh
bash: just docs
Workflow agent steps can embed xprompt references inline:
steps:
- name: review
agent: |
#mentor(prompt=[[Review error handling]])
See the Workflow Specification for full details on multi-step workflows, control flow, parallel execution, and human-in-the-loop approval.