Skip to content

Plan format

This page describes the structure and validation rules for SASE plan files.

Plan Frontmatter Schema and Validation

Run sase plan validate PLAN_FILE while authoring a plan. The command selects the schema from the plan's required top-level tier: tale or tier: epic property; the former -t/--tier option has been removed. Ordinary schema validation accepts any readable UTF-8 path and does not require SASE_AGENT, SASE_ARTIFACTS_DIR, or a registered project context. Memory decisions also use host context for selector and human-quote checks inside an agent; outside an agent, quote verification is deferred to proposal. Validation reports problems in one pass with stable diagnostic codes, field paths, and best-effort line numbers.

Every tale and epic requires these authored fields:

Field Required Rule
tier yes tale or epic; selects the validation schema
title yes Non-empty human-readable plan title
goal yes Non-empty description of the outcome the plan is intended to reach
model no Non-empty model value using the same syntax as %model

Tales additionally require size: xsmall | small | medium. A tale is work one follow-up agent implements directly, so large and xlarge are invalid tale sizes and belong in an epic plan instead. Authoring validation rejects a missing or over-sized tale size, while launch validation normalizes both to medium with a warning so existing tales launch.

SASE-managed create_time, status, bead, bead_id, proposed_by, proposed_with, and project fields are accepted but never required. proposed_by records the proposer; proposed_with records its provider/model when known. project records the SASE project for a machine-local proposal and is stripped when the plan is archived into that project's plan store. A plan may also declare review decisions; their answers and review stamps are written by SASE. A proposal may carry the transient links: authoring inlet: a list of typed artifact relationships that sase plan propose validates, persists, and removes before archiving; see Authoring links in a proposed plan. Plans with a prompt or parent property remain valid, but both are intentionally omitted from canonical schema and authoring output because new links use the Markdown header block; parent additionally reports a deprecation warning pointing at the PARENT bullet. See the plan-frontmatter migration note. Epics may also carry the SASE-managed parent_bead field. Other unknown fields are errors. A plan must start with valid, closed YAML frontmatter and contain a non-empty Markdown body.

Epics additionally require an ordered, non-empty phases list. Optional patch (changespec is also accepted) and integer bug_id metadata may be supplied; bug_id requires the Patch association. The epic-only parent_bead associates an approved plan with the bead under which SASE creates its child epic. Each phase requires a unique slug id, a non-empty title, a depends_on list, and size: xsmall | small | medium | large | xlarge. Dependencies may only name earlier phases, cannot repeat, and cannot refer to the phase itself. Optional phase fields are description and model. Only set a phase model when the user's prompt requested one; for a phase that only exercises or observes a SASE agent feature and does no consequential work, use size: xsmall instead of a cheap model override.

---
tier: epic
title: Workspace GC rewrite
goal: Stale workspaces are collected safely
phases:
  - id: core
    title: GC planner and safety checks
    depends_on: []
    size: medium
  - id: cli
    title: Workspace GC command
    depends_on: [core]
    size: small
---
# Plan

Implement the validated workflow.

Epic-only fields on a tale produce warnings because they are inert when a human downgrades an epic-authored plan. All other schema violations are errors. Human failures always include the authoritative expected-schema table and a minimal valid example. Add -e/--explain to print tier-specific authoring guidance before the result; in JSON mode the same guidance is included as an explanation field. -q/--quiet suppresses a successful human summary, but an explicitly requested explanation is still printed.

If tier is missing or is not tale or epic, validation fails with an actionable hint and omits the explanation because no authored schema can be selected. To keep reporting the other problems in the file, the implementation uses the tale schema as a diagnostic fallback. Consequently, JSON reports "tier": "tale" and the expected tale schema in this error case; that value does not mean SASE inferred a tale. The tier diagnostic still fails the command. JSON stays on stdout while the tier hint is written to stderr. Otherwise -j/--json returns schema_version, ok, the authored tier, path, the complete diagnostics list, and the expected schema. Exit status is 0 for valid plans, 1 for validation failures, and 2 for command-usage errors.

Plan Decisions add human text to sase plan validate output when the plan has decisions. Outside an agent, the command prints quote verification runs at propose (stdout in human mode, stderr in --json mode) before the result. After the human render, or on stderr after the JSON object in --json mode, the command also prints the Decision Sheet whenever the validated plan still has decisions. That includes the outside-agent path. When an auto plan-approval action is active (%auto), one more line follows the sheet: auto-approved: every decision takes its default. That line is not printed merely because the process is inside an agent: it appears only when the live %auto spelling covers the plan's tier, so a cross-tier plan under %auto:tale or %auto:epic omits it and waits for review. At propose time the uncovered line reads %auto:<mode> does not cover <tier> plans; this plan waits for review. In --json mode stdout stays one JSON document: the sheet, the %auto note, and the outside-agent quote-verification note live inside the decisions envelope. If agent-context host checks add diagnostics, the plan is cleared and the sheet is skipped. Ordinary plans without decisions keep the JSON-only contract.

Plan Decisions

Plan decisions let a reviewer choose between explicit alternatives while approving a tale or epic. Every surface shows the decisions next to the plan with their current values filled in, and the primary action always approves exactly the values on display. Plans without decisions keep their ordinary approval flow.

A decision's lifecycle runs from authoring to the coder. sase plan validate renders the Decision Sheet the reviewer will see. sase plan propose echoes Plan Decisions: N (🧠 M) before its handoff, plus an %auto note when the plan will be auto-approved. Gate build freezes the definitions into payload.decisions and compiles decision_<id> raw properties onto the approval options. The reviewer edits values in ACE's Decisions panel, Telegram's decision keyboard, or the CLI's -D/--decide ID=VALUE; submits carry the displayed review revision, and conflicting values across selected options fail before acceptance. On approval, SASE stamps the accepted answers into the durable plan (see Archive fields below); every implementer then receives them mechanically as a host-written Reviewer decisions block, and an auto-approved plan with decisions posts one quiet receipt. Automatic resolution runs the agent's autonomy record through one core evaluator over explicit policy option IDs — never the gate's primary branch or default selections — and records the deciding policy block (profile, selection, rule, outcome, value, option_ids, source, revision, digest) on the gate's request envelope, its creation result, and, for automatic outcomes, its response.

Declare up to five decisions as a frontmatter map. Each decision needs an ask and a default. Omitting choices makes it a boolean toggle; supplying a map of two to five choices makes it a choice whose default must exactly name a choice key. An optional why explains an ordinary decision's default. This complete tale illustrates a choice:

---
tier: tale
title: Keyboard help overlay
goal: Pressing ? shows the available bindings
size: small
decisions:
  grouping:
    ask: How should the overlay group bindings?
    choices:
      pane: By pane, matching the footer hints
      mode: By keyboard mode
    default: pane
    why: Keep the same order as the footer
---

# Plan

Add a help overlay to the active pane.

> [!decision] grouping = pane Group bindings by pane.

> [!decision] grouping = mode Group bindings by keyboard mode.

Decision IDs start with a lowercase letter, contain only lowercase letters, digits, and underscores, and have at most 32 characters. Choice keys use the same pattern with a 24-character limit. Validation rejects the host-reserved IDs approve, commit, reject, feedback, coder_prompt, coder_model, wait, epic_launch_mode, and capacity. It also rejects the YAML 1.1 words y, n, yes, no, on, off, true, false, and null as both decision IDs and choice keys. Questions are one line of at most 120 characters. Choice labels and why are one line of at most 100 characters. Do not restate the choice key, or a Yes:/No: prefix, at the start of its label; the label states the consequence. There is no authored kind field: the presence of choices selects the kind.

A > [!decision] <id> block marks the true branch of a toggle; = no marks its false branch. A choice block must use = <key>. Unknown IDs or branches fail validation. The ID is the first word after the marker, and the line has a branch only when = directly follows the ID. Any other text on the header line is prose and is ignored. Prettier joins a header with its first body line, so > [!decision] grouping = pane on its own line and > [!decision] grouping = pane Group bindings by pane. mean the same thing, and a toggle header joined to prose that contains = still names only its ID. Fenced examples are ignored. An unmentioned decision produces a warning, so reference each decision in the prose or a callout. Decisions do not conditionally schedule epic phases: phases[].when is reserved and rejected.

Decisions ride the gate inputs to durable answers. ACE renders a Decisions panel above the verdict, Telegram renders a decision keyboard, and the CLI takes -D/--decide ID=VALUE (repeatable) with a decision card on every approval. To override a default on a pending gate at the wire level, use the declared decision_<id> field with sase gate answer:

# Replace REQUEST_ID with the pending tale gate's ID.
sase gate answer -k plan -i REQUEST_ID -o approve -o commit \
  --set decision_grouping=mode

For an epic, use -k epic_plan -o approve. Omitted inputs use the effective defaults. Conflicting values across selected options fail before acceptance. Feedback carries changed values into the replan as provisional choices; it does not approve the plan.

Archive fields: on approval, SASE writes each decisions.<id>.answer plus decided_by: reviewer|agent|auto and, for non-auto approvals, decided_via: tui|telegram|mobile|cli into the durable plan. Do not author those system fields in a new proposal. Stamping is idempotent, and re-stamping different values is an error; retry paths re-stamp from the recorded gate response. Committed plans are validated in Archived mode, which is as strict as authoring mode but also allows these system-written answer fields.

Decision definitions are frozen when the gate is created. SASE writes those frozen definitions beside the durable plan as <plan>.plan-decisions.json and keeps that file on approval. Every surface showing a pending or accepted gated plan renders those frozen facts, so a decision's provenance (for example "you asked") never depends on the environment of the process reading it. In-gate edits can change prose, but changing anything under decisions: is refused with an invitation to change answers in the Decisions panel or send feedback to change the questions. Changing the decision set requires a new proposal. Clients that submit a review revision are also checked against the displayed revision; a stale revision fails before execution.

A memory decision is a toggle with memory: [<selector>, ...]. Selectors use the memory-read forms: a note, a web, or a web:keyword strand. They must resolve, and separate decisions cannot cover the same resolved memory file. Use default: false for a proposed memory change the human has not requested and that does not correct a false claim. A default: true memory decision needs one quote of 3–300 characters: requested from human-authored text, or corrects copied verbatim from the note's current text, naming a claim that is false or misleading or that the plan's own work makes so. Never carry both; requested wins when the user asked. Memory decisions cannot carry why or choices.

SASE verifies requested against the original human-typed launch prompt, human plan feedback, or human free-text question answers in the planner's chain. Generated macro text, model replies, selected option labels, auto-approval, and records without authorship provenance do not establish that request. It verifies corrects against the current raw text of the memory files that decision covers. Provenance is asked or correction when the quote matches, quote_not_found or claim_not_found when it does not, and inherited when an epic already authorized the same note. An unverified quote makes the effective default false; agent-context validation and proposal reject an authored true default without a verified quote. Validation outside an agent defers quote verification to proposal. An agent cannot turn a memory decision on unless its effective default is already true. Feedback about a memory decision remains provisional. See launch authorship and the gate protocol.

When a plan-launched agent later commits, the host finalizer checks memory notes against the accepted memory decisions, including decisions inherited from the agent's epic. A note that no accepted decision covers still lands. The finalizer records a warning, memory_change_uncovered, naming each uncovered path and the plan. Agents that were not launched from a plan are not checked. If the guard cannot resolve the plan, the diff, or the binding, it records nothing and the commit still lands. Generated root instruction files (AGENTS.md and the provider shims) and sase/memory/README.md count as uncovered only when that same declaration changes no covered memory note.

_[project]: A sase project is a named unit of work registered with SASE. _[workspace]: A sase workspace is a numbered clone of a project's primary repo, managed by the workspace store and tracked in that project's registry.json. _[agent]: A sase agent is an agent session or a single agent that does not belong to a session. _[prompt]: A prompt is the text SASE hands to a provider to start an agent turn. _[macro]: A macro is a reusable prompt triggered with #name in agent prompts. _[Patch]: A Patch is SASE's local unit of change. _[bead]: A bead is one tracked work item in SASE's git-native issue tracker. _[plan]: A plan is a persisted, approved artifact recording the intent behind agent work. _[TUI]: The TUI is SASE's terminal interface for running agents and reviewing results. _[epic]: An epic is an executable multi-phase plan run with sase bead work. _[gate]: A sase gate is a durable, command-backed request for one user decision, created when work must pause for the user. _[monitor]: A sase monitor is a monitor turn that runs one long command under a detached supervisor, so the command outlives the agent that started it. *[stitch]: A stitch is the lightweight ordered change record inside a Patch's STITCHES: section.