Agent Families¶
A plan-chain agent family is the group of agents that share a ---separated base name: foo, foo--plan,
foo--code, foo--plan-2, and so on. Families are how SASE tracks a unit of work as it moves through planning,
questions, feedback rounds, coding, and commit follow-ups; ACE groups every member under the same root entry on the
Agents tab.
Historically, families only grew from the inside: the runner's plan/questions handoff was the sole mechanism that could attach a new member, along hard-coded transitions (plan approved → spawn coder, questions answered → spawn follow-up). Dynamic agent families make family extension a first-class primitive in two complementary ways:
- User-initiated extension. Attach a new member to any existing family by writing
%n(parent, suffix)in an ordinary prompt, from any launch surface (CLI, TUI, Telegram/mobile). See Extending a Family by Hand. - Lifecycle-initiated extension. Define custom roles declaratively in
kind: agent_familyYAML (for example, animprove_planreviewer after the planner, or atesterafter the coder), toggle them per approval at the plan gate, and set per-project sticky defaults. Agents themselves can request launches, gated behind an explicitLaunchApprovalpending action. See Custom Lifecycle Roles and Agent-Initiated Launches.
Glossary note: agent families use the runner's double-dash lineage model (foo--reviewer). Dot-separated names such as
foo.bar are a distinct ACE TUI concept — agent hoods and neighbors — unrelated to plan-chain family membership.
Family Roles¶
Every family member records an agent_family_role derived from its name suffix:
| Suffix | Role | Status labels |
|---|---|---|
plan, q, code, epic, legend, commit |
The corresponding built-in role | Built-in role statuses (e.g. coder statuses) |
Numeric (@ in %n allocates the next number) |
feedback (a feedback/Q&A round) |
Feedback-round statuses |
Any other word (reviewer, tester, ...) |
The word itself (open set) | Generic RUNNING/DONE, unless a custom role definition supplies display labels |
The reserved suffixes are exactly the built-in plan-chain roles; custom role definitions may not reuse them.
Extending a Family by Hand¶
The two-argument %n(parent, suffix) directive attaches a new agent to an existing family from any normal user launch
surface. %n(parent, @) allocates the next free numeric feedback suffix. If the parent is still running, the child
launches immediately as a WAITING child row under the parent and starts when that exact parent artifact completes
successfully; if the parent fails, is stopped, or is killed, the queued child is cancelled to STOPPED with a
completion notification.
Two bundled xprompts assemble the classic follow-up prompt bodies — #with_feedback (plan feedback rounds) and
#with_q_and_a (answered question rounds). They only build prompt text; %n is what launches and attaches the agent:
%n(planner, @) #with_feedback:: Add failure handling before coding.
%n(planner, @) #with_q_and_a(qa_file=/tmp/qa_rounds.json):: Continue with the base prompt.
%n(foo, reviewer) Review the diff produced by this family.
The full grammar, parent-resolution rules, queueing semantics, error messages, and the follow-up xprompt reference live
in the XPrompts doc; the #with_feedback / #with_q_and_a reference is under
Bundled Follow-Up XPrompts.
A manually attached member writes the same family metadata as a runner-created follow-up, so ACE groups, statuses, and
dismisses it like any other member. A manual attach does not run the custom lifecycle machinery, though: even when the
suffix matches a defined custom role (%n(foo, tester)), the member runs your prompt with generic RUNNING/DONE labels.
Display labels, prompt templates, and visit caps apply only to members the family evaluator inserts itself (see
Custom Lifecycle Roles).
Custom Lifecycle Roles¶
Custom family roles are defined declaratively in YAML files with kind: agent_family. A definition extends the built-in
standard_plan_chain family and adds roles that run at specific points in the chain — after the planner (gated by plan
approval) or after the coder and other terminal roles (via role-completion events).
Definition Format¶
A YAML file is recognized as a family definition only when its top-level mapping has kind: agent_family.
Top-level fields:
| Field | Required | Notes |
|---|---|---|
kind |
yes | Must be agent_family |
schema_version |
yes | Must be 1 |
id |
yes | Identifier matching ^[A-Za-z][A-Za-z0-9_]*$ |
version |
yes | Positive integer |
extends |
no | Only standard_plan_chain is accepted (and it is the default) |
roles |
yes | Non-empty mapping keyed by role id |
Per-role fields (unknown keys are load errors):
| Field | Required | Values / default | Notes |
|---|---|---|---|
suffix |
no | default --<role_id>; must match ^--[A-Za-z0-9_]+$ |
Must not collide with the reserved suffixes (--plan, --q, --code, --epic, --legend, --commit) |
prompt_template |
yes | an xprompt reference string | Validated against the xprompt catalog; format placeholders: plan_file, source_artifacts, artifacts_ref, outcome, source_role, role |
placement |
yes | mapping with required after: <role> |
after: plan binds to the plan-approval gate; after: code (and other terminal roles) binds to role completion |
on_done |
yes | re_review | continue | terminate |
Declared follow-on intent, validated and recorded in the run snapshot; looping is driven by the role's prompt template (see Loop Caps) |
on_failure |
yes | notify_and_continue | notify_and_stop |
|
auto |
yes | run | skip (no default) |
Whether %auto flows include this role; definitions without it are rejected |
max_visits |
no | positive int, default 3 |
Loop cap; at the cap the evaluator hard-stops the loop and terminates normally |
default |
no | bool, default false |
Whether the member is toggled on by default at the plan gate |
label, done_label |
no | ≤ 24 chars, ^[A-Za-z0-9][A-Za-z0-9 _/-]*$ |
Display-only status labels; see Custom Role Status Labels |
delegated_budget(s) |
no | reserved | Accepted and snapshotted but not yet interpreted |
Discovery¶
Definitions are discovered from *.yml/*.yaml files in the same directories as xprompts, with later sources
overriding earlier ones by id:
- Bundled package xprompts
- Plugin
sase_xpromptsresources ~/.config/sase/xprompts/<project>/- Workspace
.xprompts/andxprompts/directories - The general xprompt search paths
Invalid files are skipped with a recorded load issue (surfaced as skipped: <source>: <error> by sase xprompt list).
Valid definitions appear in sase xprompt list JSON output with "type": "agent_family" and a role-summary preview.
Bundled Examples¶
Two flagship examples ship as inactive templates under src/sase/xprompts/examples/agent_families/ (deliberately
outside the search path). Copy the .yml file into an active xprompts directory to enable it — the prompt templates it
references (#agent_family_improve_plan, #agent_family_tester) are already bundled and discoverable — then confirm it
appears in sase xprompt list:
# improve_plan.yml — re-review loop after the planner
kind: agent_family
schema_version: 1
id: improve_plan
version: 1
extends: standard_plan_chain
roles:
improve_plan:
suffix: "--improve_plan"
label: "IMPROVING PLAN"
done_label: "PLAN IMPROVED"
prompt_template: "agent_family_improve_plan:{plan_file}"
placement:
after: plan
on_done: re_review
max_visits: 3
on_failure: notify_and_stop
auto: skip
# tester.yml — post-coder verification
kind: agent_family
schema_version: 1
id: tester
version: 1
extends: standard_plan_chain
roles:
tester:
suffix: "--tester"
label: "TESTING"
done_label: "TESTED"
prompt_template: "agent_family_tester:{source_artifacts}"
placement:
after: code
on_done: terminate
max_visits: 1
on_failure: notify_and_continue
auto: run
Post-code members run after the coder's embedded VCS post-steps (#propose/#commit), so a tester tests the proposed
change; testers do not block the propose step.
Choosing Members at the Plan Gate¶
When a family with defined custom members reaches plan approval, you choose which members run for that approval.
- ACE TUI: press
con the plan-approval modal to open the custom-approval dialog; it renders an "Also run:" section where digit keys1-9toggle members. Each row shows its toggle digit, a checkbox, the role id, and the role's placement — for example1 [x] tester after code. The default-checked state comes from each role'sdefaultmerged with the project config. - CLI:
sase plan approve <selector> --with <role>and--without <role>(short forms-w/-W, both repeatable). Naming the same role in both flags or naming an unknown role fails with a clear error before the approval is written.
sase plan approve abcdef12 --with tester --without improve_plan
Sticky Project Defaults¶
Set per-project defaults in sase.yml under agent_family.plan_approval.default_members, a mapping of role id to
boolean:
agent_family:
plan_approval:
default_members:
tester: true
Precedence: explicit gate selection > project config override > the role definition's own default.
Remote Approvals and Auto Modes¶
- Telegram/mobile approvals have no member toggles; the notification preview appends
Also run: <ids>so remote users see what will run, and the sticky defaults apply. %auto/%aflows only enable members that are both default-enabled (after the sticky defaults are applied) and declareauto: run. Auto plan approval itself remains limited to theapprove,tale, andepickinds.- The remote
runchoice (the Telegram/mobile "Run" button) archives the approved plan intosdd/tales/YYYYMM/exactly like an interactive Approve.
Loop Caps¶
Every role has a max_visits cap (default 3). A re-review loop arises when the role's prompt template resubmits a plan
(the bundled improve_plan template does exactly that), which brings the family back to the plan gate; each time the
evaluator inserts the role, its per-role visit count in family state increments. At the cap the evaluator stops
inserting it and the run terminates through the normal finalize path, recording the exhausted role in the run artifacts.
A custom role can never chain directly after itself.
Custom Role Status Labels¶
A role's label and done_label replace the generic status text on the Agents tab while the member is RUNNING and
after it is DONE, respectively (for example TESTING / TESTED). Labels are presentation-only: status buckets, row
colors, dismissal, mirroring, and waiting behavior all key off the unchanged semantic status. Labels come only from role
definitions — they cannot be set from the launch site.
Agent-Initiated Launches¶
User-initiated launches are never gated: prompts typed in the CLI, TUI, or Telegram/mobile — including user-typed
%n(parent, suffix) family attaches — spawn directly. When a running agent invokes sase run, the launch is
instead diverted into a LaunchApproval request: SASE previews the launch, sends a priority notification, and
spawns nothing until a human approves.
Requesting a Launch¶
Agents use the generated /sase_run skill, which teaches them to write a structured request and submit it with:
sase launch request -f launch_request.json -o json
The request JSON (schema version 1) carries schema_version: 1, a required prompt, an optional reason,
approval: "required" (the only accepted value — there is no auto-approve for agent-initiated launches), max_slots
(default 1; the planned fan-out must fit), and an optional family_type. Inline payloads
(sase launch request '<json>' or @path) and plain prompt flags (-p/--prompt, -r/--reason) are also accepted.
Request artifacts land in ~/.sase/launch_requests/<request_id>/: launch_request.json (the full preview payload plus
the normalized request) and launch_preview.md (the human-readable preview shown by approval surfaces). A request may
embed %n(parent, suffix) in its prompt to attach the launch to a family.
Approving or Rejecting¶
- ACE TUI: the launch-approval modal renders the preview; press
ato approve,rto reject,q/escape to cancel. - CLI:
sase launch approve <selector>orsase launch reject <selector> [-f <feedback>], where<selector>is the request id, notification id, or a unique notification prefix.
The response is write-once — a second resolution attempt fails as already handled. On approval the request is revalidated and re-planned fresh at dispatch time, then dispatched through the normal launch path; multi-slot batches are all-or-nothing. Rejection feedback is written into the response file so the requesting agent can read it and adjust instead of spawning anyway.
Under the Hood¶
The plan/questions handoff routes through typed events (plan_submitted, questions_submitted, role_completed)
evaluated against a family definition by the standard_plan_chain evaluator; "what happens next in a family" is
answered by data instead of hard-coded branches. role_completed fires for every runner-spawned follow-up that
completes un-killed, which is the seam custom after: code roles hook into; the standard chain maps it to terminate, so
default behavior is unchanged. (Members attached by hand with %n run outside the runner loop, so their completion does
not raise role_completed.)
Family runs snapshot their definition (agent_family_config_id/version/hash) and track progress in additive
agent_meta.json fields (family_state with current role, feedback/Q&A round counts, and per-role visit counts, plus
agent_family_custom_role for custom-role members). Artifacts written before these fields existed remain valid, and an
active run always evaluates against its snapshotted definition, so editing a definition mid-run cannot destabilize the
run.
Two invariants worth knowing:
- No custom status strings. Semantic status sets are closed; custom roles get generic RUNNING/DONE semantics with display labels layered on top.
- v1 metadata is the compatibility contract. A member attached with
%nwrites the same family metadata fields as a runner-created follow-up, so grouping, statuses, and dismissal work identically without migration. Only evaluator-inserted members additionally carry the custom-role snapshot that drives display labels and loop tracking.