Workspace provider internals¶
This page documents the workspace provider contract and its core integration points.
Plugin Architecture¶
Workspace providers are implemented as pluggy plugins,
following the same pattern as VCS plugins. The core sase package bundles the
BareGitWorkspacePlugin for local bare-remote git repositories. Additional backends
must be installed in the same Python environment as sase:
| Package | Plugin | Description |
|---|---|---|
sase (core) |
BareGitWorkspacePlugin |
Bare-git repos (local filesystem remote) |
sase-github |
GitHubWorkspacePlugin |
GitHub-hosted repos (PR workflows via gh CLI) |
Plugins register themselves via the sase_workspace entry point group. The plugin
manager loads all registered plugins and dispatches operations through pluggy hooks.
Most hooks use firstresult=True — the first plugin that returns a non-None result
wins.
Hook Specification¶
All workspace operations are defined in WorkspaceHookSpec
(src/sase/workspace_provider/_hookspec.py). Each method is prefixed with ws_ to
namespace them within the pluggy project.
Key Data Types¶
WorkflowMetadata¶
Each workspace plugin declares metadata about the workflow type it supports:
| Field | Type | Description |
|---|---|---|
workflow_type |
string | Short name used in #type:ref prompts (e.g., "git", "gh") |
ref_pattern |
string | Regex matching #type:ref or #type(ref) syntax |
display_name |
string | Human-readable name (e.g., "Git (bare)", "GitHub") |
pre_allocated_env_prefix |
string | Env-var prefix for pre-allocated workspace variables |
vcs_family |
string | VCS family (e.g., "git", "hg") |
vcs_provider_name |
string | Specific VCS provider name (e.g., "bare_git", "github") |
sdd_storage_policy |
string | Optional provider SDD policy declaration: in_tree or separate_repo |
Built-in metadata includes SASE_GIT for #git. Plugin packages can add prefixes such
as SASE_GH.
SASE treats sdd_storage_policy as authoritative. in_tree takes effect immediately,
as it does for the built-in bare-git provider. separate_repo requires the provider to
materialize a sidecar before SDD writes or workflow setup can continue. A positive
.sase/sdd-store.json record preserves that materialized selection for offline use.
Managed GitHub initialization can record the resolved store as sidecar_repos, routing
every configured role and (in schema 3) beads to role-specific linked clones. Schema-2
records keep beads under the plans clone. This is a materialized-store layout, not an
additional provider policy value.
ResolvedRef¶
Result of resolving a workspace reference:
| Field | Type | Description |
|---|---|---|
project_file |
string | Path to the project spec file |
project_name |
string | Name of the project |
primary_workspace_dir |
string | Path to the primary workspace directory |
checkout_target |
string | Branch or revision to check out |
extra |
dict[str, str] | Additional plugin-specific data |
canonical_ref |
string | null | Optional stable ref to persist when the matched prompt ref was a provider-specific locator or path |
For clone-based git workflows, primary_workspace_dir is the primary checkout path and
get_workspace_directory() derives numbered sibling workspaces from it. Some provider
plugins can leave primary_workspace_dir empty and resolve numbered workspaces through
their own helper command.
When canonical_ref is set, launch history, replay selections, and prompt MRU entries
use #<workflow>:<canonical_ref> instead of the raw user-entered locator; when it is
absent, SASE keeps the matched ref. For example, first-use repository paths or GitHub
owner/repo refs can resolve to a stable SASE project key while still letting the
provider decide the checkout target. canonical_ref does not replace checkout_target;
providers still decide which branch or revision to check out.
Hook Reference¶
Metadata and Detection¶
| Hook | Returns | Description |
|---|---|---|
ws_get_workflow_metadata |
WorkflowMetadata \| None |
Declare this plugin's workflow type metadata |
ws_detect_workflow_type |
str \| None |
Detect workflow type from a project file |
ws_get_change_label |
str \| None |
Get the change label (e.g., "PR", "PR") |
ws_get_workspace_name |
str \| None |
Get the workspace/project name for a CWD |
ws_get_workflow_metadata is the only hook that collects results from all plugins
(not firstresult). This allows the registry to build a complete map of all available
workflow types.
Reference Resolution and Workflow Setup¶
| Hook | Returns | Description |
|---|---|---|
ws_resolve_ref |
ResolvedRef \| None |
Resolve a #type:ref reference to workspace info |
ws_setup_workflow |
dict[str, str] \| None |
Set up environment variables for a workflow run |
ws_get_workspace_directory |
str \| None |
Get or create a workspace directory for a clone |
Change Submission and Review¶
| Hook | Returns | Description |
|---|---|---|
ws_submit |
tuple[bool, str \| None] \| None |
Submit a Patch (merge, push, etc.) |
ws_prepare_mail |
object \| None |
Prepare a change for mailing/review |
ws_extract_change_identifier |
tuple[str, str] \| None |
Extract identifier from a PR URL |
ws_supports_reviewer_comments |
bool \| None |
Check if a PR URL supports reviewer comments |
ws_generate_reviewer_comments_script |
str \| None |
Generate a script to fetch reviewer comments |
ws_generate_submitted_check_script |
str \| None |
Generate a script to check if a PR is submitted |
Commit Formatting¶
| Hook | Returns | Description |
|---|---|---|
ws_format_commit_description |
bool \| None |
Format a commit description file (add tags, prefix, etc.) |
Repository Onboarding¶
The provider-neutral onboarding flows use additive hooks. Providers return None when
they do not own the requested workflow_type; repository creation never clones or
registers the project, which happens later through the normal reference resolver.
| Hook | Returns | Description |
|---|---|---|
ws_parse_repo_ref |
ParsedRepoRef \| None |
Purely normalize a user-entered repository reference |
ws_list_local_repo_candidates |
LocalRepoCandidates \| None |
Find bounded local repositories and checkouts |
ws_probe_repo |
RepoProbe \| None |
Read-only observation of availability and repository metadata |
ws_describe_repo_creation |
RepoCreationForm \| None |
Describe provider-specific creation fields |
ws_create_repo |
RepoCreateResult \| None |
Create the remote or bare repository only |
ws_parse_repo_ref must not access the filesystem, network, or subprocesses.
ws_list_local_repo_candidates reads local metadata without subprocesses, and
ws_probe_repo is bounded and non-interactive. Creation forms identify each option by
its stable name; ws_create_repo receives those values and returns created,
exists, or failed without deciding whether to adopt an existing repository.
Register an existing repository with sase project add REF. The command previews the
provider observation and planned local changes before resolving the repository; its
optional setup step is the human-only marker commit and push. Create a new repository
with sase project create REF; --check is available to agents, and apply is
human-only unless it runs as an approved /sase_gate option or
SASE_PROJECT_CREATE_BYPASS holds a reason.
Registry Functions¶
The workspace provider package (sase.workspace_provider) exports convenience functions
that call through the plugin manager. These are the primary API for consumers:
| Function | Description |
|---|---|
clone_external_repo() |
Clone a repository by provider scheme |
create_repo() |
Create a remote or bare repository |
create_sdd_remote() |
Create the SDD sidecar remote |
describe_repo_creation() |
Describe provider-specific creation fields |
detect_workflow_type() |
Detect workflow type for a project file |
extract_change_identifier() |
Extract a change identifier from a message |
format_commit_description() |
Format a commit description |
generate_reviewer_comments_script() |
Generate a reviewer-comments script |
generate_submitted_check_script() |
Generate a submitted-check script |
get_all_workflow_metadata() |
Get metadata from all registered plugins |
get_change_label() |
Get the change label for a project |
get_display_name() |
Get display name for a workflow type |
get_display_name_by_vcs() |
Get display name by VCS provider name |
get_display_name_by_vcs_family() |
Get display name by VCS family |
get_embedded_vcs_tag_pattern() |
Get the embedded VCS tag pattern |
get_external_repo_schemes() |
Get clone schemes registered by providers |
get_pre_allocated_env_prefix() |
Get env-var prefix for a workflow type |
get_ref_patterns() |
Get all registered ref patterns |
get_sdd_storage_policy_by_vcs() |
Get an SDD storage policy declared by VCS |
get_vcs_tag_pattern() |
Get the VCS tag pattern for a workflow type |
get_workflow_names() |
Get all registered workflow type names |
get_workspace_directory() |
Get the directory for a workspace number |
get_workspace_name() |
Get workspace/project name for a directory |
list_local_repo_candidates() |
List local repositories and checkouts |
list_ref_namespaces() |
List owner/namespace candidates |
list_repo_candidates() |
List remote repository candidates |
materialize_sdd_store() |
Materialize an SDD sidecar store |
parse_repo_ref() |
Parse a user repository reference |
peek_ref() |
Read-only workspace reference lookup |
preflight_sdd_sidecar() |
Preflight an SDD sidecar remote |
prepare_mail() |
Prepare a change for review |
probe_repo() |
Observe repository availability and metadata |
reset_workflow_metadata_caches() |
Drop cached workflow metadata |
resolve_ref() |
Resolve a workspace reference |
submit_patch() |
Submit a Patch |
supports_reviewer_comments() |
Whether the provider supports reviewer comments |
Bare-Git Reference Auto-Initialization¶
The bundled bare-git provider resolves #git:<ref> in four modes:
- A registered bare-git project shorthand, using
~/.sase/projects/<name>/<name>.sasewhen it containsBARE_REPO_DIRandWORKSPACE_DIR. - A Patch name found across registered projects.
- A missing project shorthand with no slash, which initializes a new bare-git project
using
~/.sase/repos/<name>.gitas the bare repository and~/projects/git/<name>/as the primary checkout. - A bare repository path, deriving the project name from the path basename and creating
the matching ProjectSpec with that bare path and the default
~/projects/git/<name>/primary checkout path.
The missing-project shorthand is intended for first use from a macro or prompt bar:
#git:new_tool #!workflow creates the bare-git project on demand.
An existing ProjectSpec is never treated as a missing bare-git project merely because
BARE_REPO_DIR is absent. If the spec belongs to another workspace provider, #git:
fails with a provider-mismatch error and points to the matching VCS tag instead of
rewriting the project in place. A genuine bare-git project whose checkout still has a
local-path origin can repair a missing BARE_REPO_DIR automatically.
The same guard covers names another project already claims. Before a slashless <ref>
that has no ProjectSpec directory of its own is treated as a Patch name or a new
project, the resolver checks whether an existing project declares it as its
PROJECT_NAME or as an alias. A bare-git owner resolves as that canonical project; any
other owner fails with the provider-mismatch error, so #git:<github-alias> points you
to the matching tag (such as #gh:<github-alias>) instead of creating a stray bare-git
project. A bare-repository path whose basename is claimed this way resolves the same
way, and bare-git initialization likewise refuses a name another project claims.
sase doctor's project.name_collisions check warns when a project's PROJECT_NAME or
alias matches another project's directory key, PROJECT_NAME, or alias (compared
case-insensitively, disabled projects included), or claims the reserved home ref. The
VCS-reference history also removes provider-mismatched alias entries.
#git:home is special because it is the default for bare prompts. If the home
ProjectSpec is missing, SASE bootstraps a managed empty bare-git project at the default
home paths; an existing non-bare-git home project receives the same provider guard
as any other project. To point a project at an existing bare repository, use
#git:<bare-repo-path>; the path basename becomes the SASE project name, so
#git:/path/to/home.git registers the home project.
Bare-git projects use in-tree SDD under sdd/. SASE creates or refreshes generated SDD
guide files during new project initialization, existing bare-repo registration, and
first #git or sase repo open materialization. When materialization owns the checkout
setup, SASE commits and pushes only those generated guide paths with an Initialize SDD
init commit.
Known-Project VCS Fallback¶
SASE also recognizes provider-prefixed VCS refs that target registered project names
even when the corresponding workspace plugin is not available in the current process.
Known projects are discovered from ~/.sase/projects/*/*.sase (with legacy
~/.sase/projects/*/*.gp accepted as a fallback) by reading each WORKSPACE_DIR:
entry. For example, if the sase project is registered, +sase #!some/workflow,
#gh:sase #!some/workflow, and the underscore shorthand #gh_sase #!some/workflow are
treated as VCS workspace launches rather than ordinary macro references.
Known-project fallback is lifecycle-aware. Launch pickers and broad macro/catalog
discovery include enabled projects only. Legacy inactive, archived, and closed
values normalize to disabled. An explicitly typed ref to a registered disabled project
is treated as intent to resume work: launch preparation writes PROJECT_STATE: enabled
before the workspace claim. A checkout cwd or mobile project value provides
prompt-resolution context but is not a workspace ref; a bare prompt without one defaults
to #git:home. Direct workspace claims that bypass launch preparation still fail
against a disabled ProjectSpec with an enable hint. Use sase project list --state all
to inspect disabled projects and sase project enable <project> when you want to make
that state change separately.
Configured linked repositories use hidden internal PROJECT_STATE: sibling backing
records rather than a project lifecycle state. Agents prepare them through /sase_repo.
In a SASE-launched agent run, the audited open records the repo name and kind in run
artifacts and the durable repo-open log; sase's TUI uses the artifact record for
opened-repo context, and the commit finalizer enforces the linked or external repo the
agent explicitly opened.
Non-wait launches allocate the next available numbered workspace for the project and set
the VCS update target to the provider default revision. When registered workspace
metadata provides an env prefix, SASE passes the matching <PREFIX>_PRE_ALLOCATED,
<PREFIX>_WORKSPACE_NUM, and <PREFIX>_WORKSPACE_DIR values into the child process.
Launches that start with a wait directive keep workspace number 0 until the dependency
is ready, then resolve a real workspace during normal runner setup. Before applying the
current launch context, SASE removes inherited SASE_*_PRE_ALLOCATED,
SASE_*_WORKSPACE_NUM, and SASE_*_WORKSPACE_DIR variables so nested or follow-up
launches do not accidentally reuse a stale parent workspace. When a session follow-up's
composed prompt still carries its #git: or #gh: ref, SASE then supplies fresh
pre-allocation variables for the workspace that successor actually received. The VCS
setup step adopts a numbered claim already owned by its runner instead of allocating a
second checkout, and rebinds a deferred #0 runner to the real numbered workspace once
setup reports it. A pinned session workspace names the planner's workspace directly.
When it is taken, the launch fails with the occupant named unless
SASE_AGENT_PINNED_WORKSPACE_FALLBACK=pool requests relocation to a pool workspace;
relocated children record the real workspace they received.
The matching release step is handoff- and identity-aware. It leaves the claim and checkout occupant in place when the run has written a pending plan, question, monitor, gate, or pipe handoff marker. Otherwise it releases only a matching RUNNING claim whose pid still belongs to the calling runner. Claim release and occupant cleanup are checked separately: a claim-pid mismatch is a recorded no-op for the claim, and an occupant record is cleared only when its pid matches the caller.
Relationship to VCS Provider¶
The workspace provider and VCS provider are complementary plugin systems:
| Concern | VCS Provider | Workspace Provider |
|---|---|---|
| Scope | Low-level VCS commands | High-level workspace operations |
| Examples | git commit, git diff, hg amend |
Ref resolution, submit, mail prep, workspace dirs |
| Plugin granularity | One active plugin per detected VCS type | All plugins registered, firstresult dispatch |
| Entry point | sase_vcs |
sase_workspace |
| Hook prefix | vcs_ |
ws_ |
A single plugin package (e.g., sase-github) typically provides both a VCS plugin and a
workspace plugin.
Ownership Boundary¶
SASE-initiated host, runner, and scheduled work never mutates the user's primary
checkout (#0, including the legacy #1 spelling). Automation may read it to resolve
config, remotes, branches, project identity, and sidecar metadata, but every automated
write goes through one of these mechanisms instead:
- Operational workspace leases (
sase.workspace_provider.lease) claim a numbered workspace from the unified pool (#10+), materialize and prepare its checkout from the configured primary remote, and expose a leasedOperationContext. The lease is released exactly once on success, failure, timeout, cancellation, or reboot recovery, and acquisition never falls back to the primary checkout. Plan approval, epic launches, task launches, and background bead writers (claim acquisition, periodic reconciliation, external issue mirroring) all acquire a lease — or reuse an already claimed workspace when its store is a separate workspace-local sidecar — rather than resolving a canonical primary store for writing. Checkout preparation makes up to threegit fetchattempts in total: it retries transient transport failures (connection reset, timeout, DNS) and re-checks an authentication refusal such asPermission denied (publickey)once after about 10 seconds, before failing the lease; see Service host. - Reset-and-replay conflict recovery (
sase.workspace_provider.reset_replay) recovers a stale rebase, merge conflict, or non-fast-forward publication race by hard-resetting only a live, leased, machine-owned checkout to its verified upstream tip and replaying the idempotent operation from durable inputs. It refuses to touch primary#0, an unclaimed checkout, or a user-directed/read-only context; remote unavailability and lock contention are retried or deferred, never reset. - Primary-sidecar auto-sync (
sase._sidecar_auto_sync, theauto_syncsidecar setting) is the one exception that touches a clone reachable from the primary checkout: it may fetch and fast-forward an already-materialized sidecar clone — not the primary repository itself — and only while that clone is clean, attached, and strictly behind its configured remote. Dirty, detached, diverged, remote-mismatched, or missing clones are left untouched and reported. Seeauto_syncvs.auto_clonefor the config-level distinction; sync runs both from a durable per-project/role hint recorded right after a workspace-sidecar publication and from a scheduled backstop job.
sase.workspace_provider.ownership.authorize_store_mutation and the writable_*
helpers are the shared enforcement point close to every store mutation seam, so a new
background caller cannot bypass the boundary by importing a lower-level commit helper
directly. Explicit foreground commands the user runs in their own chosen cwd — including
an agent's own git operations inside its already-claimed workspace — remain unaffected;
the boundary applies to SASE resolving and writing to primary on the user's behalf.
Related¶
- Contribute — the section hub.
_[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.