[06] Commit Workflows — The Pluggable Path From Diff to PR¶
Every agent eventually has to land code somewhere. SASE's commit XPrompt workflows are the small, runtime-uniform layer that turns an agent's diff into a commit, a proposal, or a pull request — without the agent caring which VCS is underneath.
[05] covered how a plan turns into a fleet of phase agents that produce code. This post covers what happens at the end: how each of those agents lands its diff somewhere durable, and how that path is uniform across runtimes and VCS providers.
One CLI, Three Outcomes¶
The sase commit command drives three XPrompt workflows. They share the same orchestrator, the same pre-stages, and the
same result format; they differ only in what the dispatch step produces.
| Workflow | XPrompt | Dispatch hook | What it produces | Tracking |
|---|---|---|---|---|
| Commit | #commit |
create_commit |
Git commit on current branch | COMMITS entry |
| Propose | #propose |
create_proposal |
Saved diff file | COMMITS entry |
| PR | #pr |
create_pull_request |
New branch + PR | ChangeSpec |
Every flow walks the same pre-dispatch pipeline: bead association → bead lifecycle close (skipped for proposals) → plan
handling → precommit command → parent CL detection (PR only) → diff capture → checkpoint. Only then does it call the
VCS-specific create_commit / create_proposal / create_pull_request hook. Post-dispatch, the workflow writes a
commit_result.json marker for the XPrompt post-steps to read.
The Stop-Hook Contract¶
Agents do not run git commit themselves. They make changes; when they finish, the sase_commit_stop_hook notices the
uncommitted state and blocks them with a structured instruction to invoke the matching commit skill (for example
/sase_git_commit for git-based projects, /sase_hg_commit for Mercurial). The skill calls sase commit with flags —
usually -M commit_message.md -f <files> -t <method>.
That stop hook is runtime-uniform. Codex receives a structured JSON response with decision=block, Gemini and Qwen
receive structured JSON with decision=deny, and Claude-compatible hooks receive stderr plus a blocking exit code. The
control flow is the same regardless of which runtime is on the other end: changes exist → block with skill name → skill
calls sase commit. No runtime-specific branching in the agent prompt, no "if Codex then X" anywhere in the workflow.
If SASE_BEAD_ID is set, the stop hook first asks the agent to decide whether the uncommitted changes were made in the
current session. For changes the agent did make, it instructs the agent to close and verify the bead before invoking the
commit skill. That keeps bead lifecycle state ahead of the commit dispatch while avoiding accidental closure of
unrelated dirty work.
Runtime-Uniform Commit Skills¶
Every supported agent runtime ships the same commit skill surface. The skill names, flags, and outputs are identical across Claude, Codex, Gemini, Qwen, and OpenCode. An XPrompt that calls into the commit workflow runs the same way regardless of who is on the other end. This is one of the gotchas baked into SASE's project memory: do not introduce runtime-specific special cases; treat all runtimes uniformly.
The VCS Provider Boundary¶
The three dispatch methods are pluggy hooks defined in VCSHookSpec:
| Plugin | create_commit |
create_proposal |
create_pull_request |
|---|---|---|---|
BareGitPlugin |
Commit + push | Save diff + clean | Branch + commit + push |
GitHubPlugin |
Inherits from git | Inherits from git | + creates PR via gh CLI |
HgPlugin |
hg commit + mail |
sase_hg_clean |
Not supported natively |
All hooks return tuple[bool, str | None] — a success flag and an optional result string (commit hash, diff path, or PR
URL). The orchestrator stays VCS-agnostic; new VCS providers slot in by implementing the three hooks. Provider selection
is also pluggable: an env var (SASE_VCS_PROVIDER), then sase.yml vcs_provider config, then auto-detection.
Documented in vcs.md.
Resume After Conflict¶
When a VCS dispatch hits a merge conflict mid-flight, the workflow leaves a checkpoint on disk
($SASE_ARTIFACTS_DIR/commit_state.json, or ~/.sase/commit_state/<session>.json if no artifacts dir is set) and exits
with RunResult.CONFLICT (exit code 2). The CLI prints:
create_commithit a merge conflict: … Resolve the conflict, then runsase commit --resumeto finish.
sase commit --resume loads the checkpoint, re-checks the working tree for conflict markers, verifies the commit at
HEAD matches the subject line from the checkpointed message, calls the provider's vcs_finalize_commit hook to replay
idempotent post-commit work (bead amend, push with retry), re-runs the tracking steps (COMMITS entry append, ChangeSpec
creation), and deletes the checkpoint on success. Resume is VCS-agnostic: the same --resume flag works for commits,
proposals, and PRs.
This is the recovery path that makes multi-agent execution survivable. Without it, a conflict on phase 3 of a
seven-phase epic would mean wiping the workspace and restarting; with it, the human resolves the conflict, runs
sase commit --resume, and the rest of the epic carries on through AXE's %wait resolution.
What's in commit_result.json¶
After a successful dispatch, the marker contains the durable hand-off between the agent that wrote the code and whoever (or whatever) consumes the result:
{
"method": "create_commit",
"result": "<commit_hash | diff_path | pr_url | null>",
"message": "The commit message",
"name": "Branch/CL name",
"bead_id": "Bead ID if SASE_BEAD_ID was set",
"changespec_name": "ChangeSpec name (PR only)",
"entry_id": "COMMITS entry ID (commit/propose only)",
"diff_path": "Saved pre-dispatch diff path, when available"
}
XPrompt post-steps read it, emit metadata outputs (meta_new_commit, meta_commit_message, meta_changespec, …), and
downstream workflows consume those.
The Public Plugin API¶
Two entry-point groups are the public extension surface:
sase_vcs— provider classes that implementcreate_commit,create_proposal,create_pull_request, plus resume and classification hooks.sase-githubis the canonical out-of-tree implementation.sase_xprompts— packages whosexprompts/directories contribute reusable XPrompts and workflows, including overrides for the built-in commit XPrompts.
Plugin resource loading can be disabled via environment variables for debugging:
| Variable | Effect |
|---|---|
SASE_DISABLE_PLUGINS |
Disable resource plugin loading for config and xprompts |
SASE_DISABLE_PLUGIN_XPROMPTS |
Disable xprompt/workflow resource plugins only |
SASE_DISABLE_PLUGIN_CONFIG |
Disable plugin default_config.yml resource loading only |
The VCS, workspace, and LLM provider registries load entry points directly and do not consult these disable switches; those are about which configuration and prompt files contribute to the resolver, not which providers exist.
What To Read Next¶
- Commit workflows — full pipeline, CLI flag table, result schema, resume protocol, environment variables, design principles.
- Plugins — entry-point groups, discovery, writing new VCS / workspace / LLM / xprompt / config plugins.
- VCS providers — provider selection tiers, per-command VCS usage, provider-specific details.
- [07] ChangeSpecs in Practice — Review State Outside the Chat — what the commit/PR flow writes to, and how ACE reviews it.
Series Navigation¶
This is [06] in the SASE Blog Series.
- Previous: [05] Beads and SDD — Planning Multi-Agent Work That Actually Lands.
- Next: [07] ChangeSpecs in Practice — Review State Outside the Chat.
- Continue reading: SASE Blog Series, blog home, or commit workflows guide.