Skip to content

Architecture

SASE is a Python orchestration layer for agentic software engineering, backed by a required Rust core for selected deterministic data operations. The system keeps work state outside any one chat transcript so agents can be launched, tracked, resumed, reviewed, retried, and handed off through stable project artifacts.

SASE component communication diagram

System Boundary

Area Responsibility Main References
CLI Top-level sase commands, argument parsing, dispatch, and JSON helper bridges. CLI reference
sase's TUI Interactive TUI for Patches, agents, notifications, artifacts, and service status. sase's TUI
Service host Per-machine supervisor for configured daemon procs and transient oneshots; it owns the scheduler and mobile gateway procs. CLI reference
Axe Background orchestrator for scheduled hooks, mentors, workflow checks, comments, cleanup, and digests. Axe
Macro Prompt templates, reference expansion, directives, typed inputs, and reusable workflows. Macros
Workflows YAML multi-step execution with agent, bash, python, parallel, loop, and human checkpoint steps. Workflow spec
Gates Durable, command-backed user decisions and processless session-turn handoffs. Notifications
Patches PR-sized review records with lifecycle state, stitches, hooks, comments, mentors, and timestamps. Patches
Memory Always-loaded and on-demand context, explicit flat-note macro inclusion, audited reads, and sase's TUI-backed note/strand changes. Memory
SDD Durable prompt, tale, epic, and research artifacts. SDD
Beads Git-portable issue/dependency tracking and executable epic launch plans. Beads
Providers Pluggable LLM, VCS, workspace, config, and macro boundaries. Plugins
Rust core Required sase_core_rs extension for ported parsing, query, notification, agent scan, launch and admission, retention, and bead data operations. Rust backend
Integrations Public helpers and fixed bridge APIs for editors, mobile gateway, and external packages. Integrations

The Python host owns user-facing orchestration, plugin calls, subprocess handling, filesystem context, TUI rendering, and workflow side effects. Rust owns reusable deterministic backend operations that need speed, stable wire contracts, or cross-frontend consistency.

Agent Launch Flow

Most agent work enters through sase run, sase's TUI, validated axe job proposals, bead epic execution, or mobile/editor helper bridges. The launch path follows the same shape across those entry points:

  1. Parse prompt text, directives, and optional multi-prompt separators, then expand +<project> project tags and canonicalize ProjectSpec aliases in launch-bound VCS refs. For example, +bob expands to #gh:bob-cli (and #gh:bob canonicalizes to the same stable directory-key ref) before history or artifact snapshots are written.
  2. Expand the swarm, repeat, or alternative directives needed to determine the launch slots and validate their names.
  3. Resolve each slot's workspace reference, such as +<project> or a plugin-provided form. An explicit ref to a disabled known project re-enables that project before the claim. Direct claims that bypass this launch preparation remain blocked by the workspace claim guard. Providers may return a canonical_ref for a raw locator such as a first-use owner/repo ref; launch metadata, history, and prompt MRU entries then use that stable ref.
  4. Prepare fixed or deferred workspace metadata. For a normal launch, claim the final numbered workspace atomically immediately before spawning the process.
  5. In the detached runner, wait for %wait dependencies and time floors, then pass runner admission: no active agent hold may match the run, and its queue weight must fit the global max_running_agents budget or its own %queue capacity budget. A deferred-workspace launch claims its numbered workspace only after admission.
  6. Continue macro or workflow processing and invoke the selected LLM provider or workflow executor.
  7. Stream subprocess output, write chat history, and persist launch metadata.
  8. Record agent artifacts such as prompts, diffs, generated Markdown PDFs, images, plans, and explicit files.
  9. Emit notifications and update sase's TUI-visible status.
  10. Hand review, revert, restore, or commit work to the VCS and workspace provider layers when requested.

When the typed_launch_units beta flag is enabled, user-initiated sase's TUI and sase run submissions, approved LaunchApproval requests, and typed AXE job proposal batches share one typed admission path. Recursive macro expansion and fan-out still happen first, keyed {@<id>} agent-name markers resolve once across that expanded batch, then Rust builds an immutable LaunchPlan of tagged Agent or Proc units with stable logical IDs, the complete %id/%clan identity binding, waits, optional %if predicates, and code digests. Dispatch reconstructs grouping directives from that binding instead of a positional name alone. Direct user submissions persist that plan in a durable bundle and dispatch immediately; agent-initiated launches freeze the same digest behind LaunchApproval; AXE job batches containing an active %if/%proc directive dispatch through the same durable bundle under the internal axe_chop source surface, with the originating job run owning the bundle across process restarts (see Structured Results and Launch Proposals). Within an AXE job clan, dispatch — not planning — owns the declarer decision: the first surviving (eligible, dispatched) member of an undeclared clan claims it durably, so a %if-skipped statically planned declarer never leaves later members joining a clan nobody declared. AXE job proposal wait_on edges also have one job-specific dispatch effect: admission consumes the logical edge for launch ordering, then an admitted Agent unit receives a restored named-agent %wait for the nearest earlier unit that actually launched. Generic typed logical waits outside AXE remain coordinator-owned and are not re-emitted into rebuilt agent prompts. A detached launch-admission coordinator — infrastructure owned by the launch-request bundle, not an Agents-tab row — journals each unit through reserved, waiting, checking, eligible, and dispatching states to a terminal launched, skipped, condition-error, launch-error, or cancelled state. Waits resolve before conditions. For a selected managed project, %if briefly claims and prepares a numbered operational workspace, runs the predicate from that checkout, and releases the claim before dispatch. Home/unmanaged conditions have no claimable workspace and keep using their explicit source cwd. A false %if is still terminal without allocating a runner, agent identity, proc identity, or model request. Eligible Agent units still use the established agent launch path. An eligible %proc unit stays undispatched while an active agent hold matches it, and one that authors %queue fields must also fit the shared runner-capacity budget; it then dispatches as a native named-proc record with origin prompt-proc (pre-rename sase-core builds write the legacy lifecycle spelling proc-shell; sase reads both). Restarts replay the journal instead of re-running settled predicates or duplicating reserved identities.

Detached launches appear in the agent registry and sase's TUI Agents tab. Multi-prompt launches create a sequence of detached agents. Stand-alone %proc named procs appear in the same Agents tab as top-level ▣ rows backed only by the proc store, counted separately from agents. Workflow launches persist step state so sase's TUI and axe can inspect progress and recover meaningful output.

Agent holds are durable reverse waits. sase agent hold arms a hold in ~/.sase/agent_holds.json that selects agents or named procs by name, tribe, or hood; it can freeze the WAITING/QUEUED agents already in scope and fence launches submitted later. Runner admission and undispatched %proc dispatch both consult active holds. A hold ends when it is released, when its armer settles or exits, or when its TTL expires, and a broken hold store fails open rather than stranding a waiter. The %hold directive describes the same selectors in prompt text. Typed launches pre-arm the declared hold before admission can dispatch the unit, then rebind it to the running agent or proc; terminal units that never dispatch release their pre-armed holds.

The service host is the machine-level supervisor for configured daemon procs. Its service.procs catalog comes from builtin, plugin, user, and machine-overlay config; project-local entries are intentionally ignored. The shipped scheduler proc runs AXE routines and jobs, while the shipped gateway proc is disabled by default. Native user units (systemd --user on Linux and LaunchAgents on macOS) can keep the host alive across TUI and login-session lifetimes. sase axe start|stop|restart|status is an alias of the matching sase scheduler command, which routes through the scheduler service proc on the host.

On Linux, when detached work starts inside a SASE-owned systemd unit or scope (such as sase.service or another sase-* scope or service) or anywhere under a reachable user systemd manager (a tmux pane, terminal, or ssh session scope), agent runners, launch-admission coordinators, proc and monitor supervisors, the scheduler's hook/checks/mentor/workflow runners, file-hook batch runners, the bead sync worker, and the chat-install worker move into their own transient user scopes with OOMPolicy=continue, so restarting that service — or an OOM teardown of the launching terminal scope — does not kill them. SASE_DETACH_SCOPE_DISABLE=1 turns this off.

An agent runner's sase-agent-* scope bounds the lifetime of every process the agent starts. Work that must outlive the runner must escape through detach_scope into its own scope. Anything still in the scope that is not the runner, not one of the runner's live descendants, and not a spared shared daemon is a leak: it is terminated at runner exit and again between in-process successor turns (so one turn's leaked loop cannot spin through the next turn), with a periodic reaper backstop for scopes whose runner died without cleaning up. Shared user daemons — ssh-agent, gpg-agent, ssh ControlMaster, and the tmux server — are never killed. See agent_scope_teardown in docs/configuration.md.

Agent, Monitor, and Gate Turns

A SASE agent is either one standalone agent turn or a sequential session of turns. Turns were formerly called shells. Agent turns are provider/LLM turns. Monitor turns are supervised commands attached to that session. Gate turns are non-LLM members that own a durable user decision; while a gate is pending, it has no provider or command process. All three appear in one session timeline. Read the three kinds like a chat transcript: an agent turn is the assistant speaking, a monitor turn is a tool result arriving, and a gate turn is the human's move. Once their evidence is readable, later session forks can include the agent transcript, monitor log, or gate decision record.

Questions, plan review, agent-side workflow HITL, agent-initiated launch approval, and beta sudo requests use gate turns. At the handoff boundary SASE persists the verified gate bundle, names the turn, transfers or releases the workspace claim according to policy, releases the runner slot, and ends the provider turn. A client later selects a branch; SASE first writes a write-once decision receipt, so the choice is durable and a conflicting answer fails before any command runs. It then runs the branch's hashed commands (normally in a supervised detached proc), records their output, settles the turn, and optionally launches the next agent-turn session member. The answered branch can inherit a default follow-up; timeout, stopped, failed, and lost outcomes require explicit follow-up policy. This makes a human pause durable without holding a provider process or runner slot open.

State Model

SASE avoids making a live chat session the source of truth. The durable state lives in files and stores that can be inspected by users, agents, and automation:

The project-adjacent taxonomy has three non-overlapping roles:

  • A project is a named unit of work registered by a valid first-use VCS macro argument and backed by ~/.sase/projects/<name>/<name>.sase. Its user-facing lifecycle is exactly enabled or disabled; missing state means enabled. An internal sibling backing marker supports linked-repo claims but is not a project state.
  • A repo is a primary project repo, an SDD sidecar repo, or a configured linked repo. One project can therefore own several repos.
  • A workspace is a numbered clone of a project's primary repo, tracked by that project's workspace registry and claimed by one SASE agent until completion. Linked/sidecar checkouts materialized within it remain repos, not workspaces. By default a managed Git workspace borrows the primary checkout's object database through Git alternates, so moving or deleting the primary checkout calls for sase workspace repair.
State Location / Owner Use
ProjectSpecs <project>/<project>.sase under ~/.sase/projects/ Enabled/disabled lifecycle, primary repo, aliases, claims, and embedded Patches.
Agent metadata Agent artifact directories under ~/.sase/ Running/completed status, prompt files, output, diffs, workflow state, and attachments.
Agent archives ~/.sase/dismissed_bundles/ and ~/.sase/dismissed_agent_groups/ Dismissed-agent recovery bundles and named groups for later sase's TUI revival.
Clan records ~/.sase/agent_clans/<clan>.json Durable per-clan tribe and summary attributes keyed by clan generation, so they survive member kills, dismissals, and restarts.
SDD artifacts Provider-resolved sdd/, .sase/sdd/, or split sidecar roots Plans, executable epics, research notes, and links to canonical agents-sidecar prompts; resolve with sase repo path plans or research.
Beads The resolved SDD beads directory Issue graph, JSONL export, SQLite query cache, and epic execution metadata; current split stores use the root of a dedicated --beads sidecar, while schema-2 stores retain --plans/beads/.
Project content sase/sase.yml, sase/macros/, sase/skills/, sase/memory/, sase/repos/ Source-controlled project settings/context plus ignored workspace-scoped repository checkouts.
Home content ~/sase/macros/, ~/sase/skills/, ~/sase/memory/ User-wide reusable prompts and agent memory.
Memory audit ~/.sase/projects/<project>/memory_reads.jsonl Attributable flat-note, strand, and batched reference-memory reads.
Configuration ~/.config/sase/sase.yml, overlays, project sase/sase.yml Provider selection, axe jobs, mentors, macros, telemetry, mobile gateway, and defaults.
Notifications Notification store facade backed by Rust operations User-visible actions, unread state, agent completion, errors, and mobile events.
Interaction gates ~/.sase/interaction_requests/<kind>/<request-id>/ Immutable request bundles, owned commands and resources, write-once decision receipts, and terminal responses for user decisions.
Procs ~/.sase/procs/ Rust-owned proc rows, logs, and runtime directories for %proc named procs, gate answers, and other supervised background commands.
Agent holds ~/.sase/agent_holds.json Durable reverse-wait holds consulted by runner admission and undispatched %proc dispatch.
Service state ~/.sase/service/ Service-host lock, status, machine enablement/stop overrides, proc start/restart requests, captured environment, and bounded host/proc logs.
Managed temp $SASE_TMPDIR, else ~/.sase/tmp/ Per-launch agent scratch, Cargo targets, handoff files, and workflow scratch, bounded by the owner reaper and runner-exit cleanup.
Workspace claims Running-field state and provider metadata Reservation and release of numbered workspaces for parallel agents.
Workspace stores Per-project registry.json under the configured workspace root Checkout paths, role/materialization, pins, generation, created/last-used times, and cleanup eligibility.
Workspace rescue ~/.sase/projects/<project>/rescue/<YYYYMM>/, else ~/.sase/rescue/ Git bundles, worktree patches, and restore manifests for state rescued before a numbered workspace or sidecar clone is healed, evicted, or re-created; reaped after 30 days.

~/.sase is the default SASE state root. Set SASE_HOME to move that root for isolated tests, alternate profiles, or containerized runs.

Every SASE-owned disk root has an owner that decides its retention. sase disk list attributes usage to those owners, and sase disk reap previews their cleanup passes or, with --apply, delegates cleanup to them: the managed-temp reaper, proc runtime sweeps, agent artifact-run retention, and workspace Git object compaction. Unowned Cargo-shaped strays are reported but never deleted. The hourly axe disk_pressure job runs the owner-safe passes early when free space runs low. See sase disk and axe housekeeping.

The canonical project/home namespace and legacy read boundary are documented in Canonical SASE Content Layout. Global config, .sase runtime state, package resources, and plugin resources deliberately remain outside that namespace.

This model lets sase's TUI, CLI commands, axe, and future frontends read the same engineering state without depending on one terminal session.

Provider Boundaries

Provider abstractions keep SASE above any single agent runtime, version-control host, or workspace strategy:

Provider Layer What It Owns Details
LLM provider Agent CLI selection, concrete model mapping, subprocess invocation, retry defaults, usage metadata. LLM providers
VCS provider Diff, checkout, commit, amend, proposal/PR dispatch, reword, submit, sync, revert, restore, and review metadata. VCS providers
Workspace provider Workspace reference resolution, workspace directory allocation, submit/mail preparation, workflow metadata. Workspaces
Resource plugins Extra macro/workflow files and default configuration. Plugins
Integration APIs Public Python helpers and fixed JSON bridge contracts for sidecar tools. Integrations

Core SASE ships built-in providers for common local use: bundled LLM provider entry points, plain-git VCS support, and bare-git workspaces. Optional packages can add hosted VCS workflows, notification delivery, editor integrations, or extra prompt resources.

Rust Core Boundary

The required sase_core_rs extension is the shared backend boundary for deterministic logic that benefits from a stable wire contract or from being reused by non-Python frontends. Current Rust-backed areas include:

  • Patch parsing and batch query operations.
  • Project lifecycle parsing, canonical enabled/disabled normalization, the true-project predicate, VCS-kind derivation, update planning, and lifecycle-filtered project listing.
  • Status transition planning.
  • Git command output parsing.
  • Notification JSONL reads and mutations.
  • Agent artifact scanning and persistent indexing.
  • Agent launch preparation, timestamp allocation, fan-out planning, low-level detached spawn, and workspace-claim planning.
  • Bead read, mutation, JSONL, SQLite, single-store ID allocation, and deterministic work-plan operations.
  • Agent hold validation, runner-capacity snapshots, and gate decision acceptance and lifecycle policy.
  • Managed-temp reaping, disk inventory and pressure classification, and agent artifact-run and proc runtime retention.
  • Git object-sharing plans for managed workspaces and failure retryability classification for gh and network Git calls.

The frontend-neutral repo_inventory.py and workspace_provider/inventory.py adapters currently compose those Rust-owned project records with Python-owned linked-repo configuration, SDD records, workspace registries, and claim parsing. CLI and TUI surfaces consume the same adapters. They are explicit migration seams for a future Rust core API, not presentation logic.

The Python host still owns side effects that require app context: plugin dispatch, VCS/workspace calls, process signalling, file locks, TUI rendering, user confirmation, macro lookup, and workflow orchestration. See Rust backend for the complete operation list and facade map.

Need Page
Command discovery CLI reference
Contributor setup and source orientation Development
Runtime operations sase's TUI, Axe, notifications
Durable work records Patches, memory, SDD, beads
Prompt and workflow execution Macros, workflow spec
Extension boundaries Plugins, LLM providers, VCS providers, workspaces
Backend boundary Rust backend