Skip to content

Documentation

Workflows

Templates that define what an agent does for each trigger kind.

Workflows

A Workflow template is a named sequence of Steps tied to a trigger kind. When something happens, such as a labelled issue, PR feedback, a retry click, or an unmergeable branch, Syrus creates a Workflow from the matching template and starts its first Step.

Templates are mostly linear happy paths with bounded loops and declared typed failure branches:

Workflow(trigger_kind) -> Step -> Step -> Step

Each Step owns one or more Runs. A Run is an attempt to execute that Step. Retries create new Runs without erasing the old transcript.

Under the hood, Work Intents and Work Units now sit between a Job and its Workflows. A Work Intent records the desired work, such as "land this Job" or "repair this CI failure"; a Work Unit records one scheduler-owned attempt to do that work. The UI normally stays focused on Jobs and Workflows, while the admin UI exposes intents and units for debugging admission control, pauses, preemption, and retries.

Admin repair actions are preflighted before confirmation and revalidated when confirmed. Job-state reconciliation handles diagnosed state drift, stale-work cancellation handles only cancellable queued/running work, and terminal WorkUnit cleanup can retire stale runtime ownership and release its locks without changing the Job, pull request, or terminal Workflow.

Built-In Templates

Initial

Trigger: a GitHub issue with the repository's trigger label, or a new cron or direct Job that uses the standard issue-to-PR path. Steps: prepare -> implement -> [adversarial review loop] -> [visual review loop] -> grade loop -> coverage_analyze -> dependency_audit -> summarize -> test_plan -> pr_open. The agent makes and commits the change during the top-level implement step. Optional adversarial and visual review loops can ask for revisions before grading. The grade loop then runs the repository's grade: configuration for the current phase, and failed required graders feed the next bounded repair iteration. coverage_analyze parses coverage artifacts produced by graders when coverage: is configured in .syrus.yml, evaluates thresholds, and pre-renders a PR comment body when pr_comment: true. summarize collects PR copy via MCP and amends the commit message. test_plan stores reviewer-facing checks, which pr_open appends as a Test Plan section before pushing the branch and opening the pull request. When a coverage PR comment body was pre-rendered, pr_open posts it as a GitHub comment on the newly created PR. For GitHub issue Jobs, the implement prompt includes the original issue title and body plus subsequent issue comments in chronological order, so clarifications added before the Run starts are part of the agent context. A successful Initial workflow leaves the Job open with a PR number attached.

PrFeedback

Trigger: new review feedback or PR comments on an existing Syrus PR. Steps: prepare -> [adversarial review loop] -> [visual review loop] -> grade loop -> coverage_analyze -> coverage_pr_comment -> summarize_amend -> try(push). The agent receives the new comments plus PR context, commits follow-up changes on the existing branch, and graders can force another bounded response iteration before summarize_amend rewrites the follow-up commit message. A successful workflow pushes to the already-open PR. If the remote PR branch advanced before the push, Syrus first tries a mechanical rebase; if that conflicts, it expands a recovery branch: push_agent_rebase -> grade loop repaired by landing_fix -> push_after_rebase.

ChatFeedback

Trigger: operator-confirmed feedback proposed from Syrus Chat on an implemented or approved Job. Steps: prepare -> [adversarial review loop] -> [visual review loop] -> grade loop -> coverage_analyze -> coverage_pr_comment -> summarize_amend -> try(push). The agent receives the agreed chat feedback as structured workflow input and commits follow-up changes on the existing branch. Submitting feedback on an approved Job unapproves it so the updated PR returns to review before landing. Feedback proposed while the Job is queued or running is held as a queued pending action and becomes confirmable once the Job is implemented. Confirmed feedback on an implemented or approved Job can also wait behind unrelated active work, such as visual review; Syrus only rejects it when a chat_feedback Workflow is already queued or running for the same Job. The app API can also create this Workflow directly for implemented or failed Jobs when an operator submits feedback outside the chat flow. Push recovery uses the same remote-advanced branch as PR feedback.

Rebase

Trigger: polling finds a PR branch we control that GitHub reports as unmergeable. Steps: auto_rebase -> agent_rebase -> force_push. Syrus first tries a deterministic git rebase. If that succeeds, it cancels agent_rebase and proceeds to force_push; if conflicts remain, the agent resolves them. A successful workflow force-pushes the rebased branch and does not open or rewrite PR copy. The push uses an explicit git push --force-with-lease=<branch>:<observed_sha> lease so Syrus does not overwrite an unexpected remote update.

When the unmergeable PR has stack children, Syrus uses StackRebase instead of opening one rebase workflow per Job. Steps: stack_auto_rebase -> stack_agent_rebase -> stack_force_push. The workflow walks the stack root-first, tries deterministic rebases for each branch, and falls back to one agent run for the first conflicted branch and everything below it. After pushing, Syrus refreshes PR bases and re-checks approved Jobs for landing.

Retry

Trigger: an operator retries a failed or completed Job. Steps: prepare -> [visual review loop] -> grade loop -> coverage_analyze -> summarize -> pr_open. It has the same shape as Initial, but runs on the existing Job and branch. pr_open is idempotent: if a PR already exists, it pushes the new commits instead of opening a second PR.

AutoMerge

Trigger: an approved Job reaches the landing queue. Steps: mergeability_preflight -> prepare -> landing grade loop repaired by landing_fix -> push -> auto_merge. The final gate first verifies mergeability and then runs graders on the exact PR branch Syrus is about to merge, after any last rebase. If required graders fail, the agent receives the grader output and gets a bounded landing_fix repair iteration before the graders run again. push publishes any final fix commits, and auto_merge re-checks GitHub approval, mergeability, branch state, and repository policy immediately before calling the merge API. Because GitHub recomputes mergeability asynchronously after a push, auto_merge briefly polls for a transient unknown state to settle before deferring, so a completed green grade is not thrown away just because GitHub had not finished recomputing yet.

Landing attempts reuse a prior green grading result when the exact same head SHA has already passed required graders, or when a different commit has the same Git tree SHA, base ref, base SHA, and required-grader fingerprint. Repositories can also opt into Trust clean rebases (trust_clean_rebase_grade) to carry a green result across a conflict-free rebase, trading a small logical-conflict risk for landing throughput. Landing logs name the reuse path (exact_head, same_tree, or clean_rebase_carry_forward) or explain why graders ran again.

When a pull request's CI checks are red, Syrus distinguishes a failure the Job introduced from one it inherited from a base branch that was already broken. It compares the checks failing on the PR against the checks already failing on its base: if every failing check is also red on the base, the Job did not cause the breakage. That verdict, and the evidence behind it (which checks fail here, which fail on the base, and the base commit compared against), appears on the Job page and in the API.

By default an inherited failure still holds the Job, but an operator can override it — unlike a failure the Job introduced, which cannot be overridden. Repositories can opt into Land when failing checks are already failing on the base branch (land_on_inherited_check_failure) to skip that hold entirely and keep landing while the base is broken. Check names are coarse, so how safe this is depends on how granular a repository's checks are; it never excuses a check that is red only on the PR.

In the dashboard, the landing queue treats each Epic as one contiguous landing unit. The Epic's child Jobs stay grouped together, with their internal order still following parent and dependency relationships, so the displayed queue positions match the order Syrus uses when landing the individual Jobs. Epic merge-trains use the same dependency-aware child ordering when building the integration branch.

MergeTrain (Epic merge-train)

Trigger: an Epic whose every open child is approved. Steps: merge_train_assemble -> merge_train_build -> merge_train_reconcile -> prepare -> landing grade loop repaired by landing_fix -> merge_train_land.

Instead of landing an Epic's PRs one at a time (each rebased onto the previous merge and graded again), the train integrates all of the Epic's children — topologically sorted by dependency — into a single integration branch, runs the graders once on the combined tree, lets the agent commit reconciliation fixes if needed, and then lands the whole branch in a single atomic merge. The child PRs are closed with a back-link to the integration merge and their Jobs marked merged. If a retry rebuilds the same integration SHA or the same integration tree that already passed required graders for the same base and grader configuration, Syrus reuses that signal and proceeds directly to landing.

The guarantee is Epic consistency: an Epic advances as a whole, green, dependency-closed set or not at all — there are never half-merged Epics on the base branch. If the grade-and-fix loop can't make the integrated tree green (or a child won't integrate), the whole attempt fails and nothing lands. The repository can declare an ordered merge_train.failure_policy ladder in .syrus.yml; when that config is confirmed absent, Syrus uses the instance compatibility default. If the config cannot be parsed or read, Syrus takes the conservative restart path. The implemented rungs are restart, which tears the failed assembly down and returns members through normal landing-failure handling; keep_assembly, which preserves a retryable failed train for the next attempt; and keep_fixes, which also preserves repair commits for stale-base rebuilds. Epic children land only via the train, never individually.

Repositories can opt into a focused multisect failure rung in .syrus.yml. When a train's required graders fail, Syrus can validate that the focused selector reproduces on the full assembly, confirm the failure is not already known flaky or inherited from the base revision, and then section the train's members to attribute a single culprit. The rung is visible in the merge-train workflow as prepare, section-evaluation, and collect/narrow Steps. Section evaluations are separate Runs, and repositories with distributed workflow DAG execution enabled can run a round's focused subset checks across immutable worker workspaces. Before narrowing to a reproducing section, Syrus also confirms the omitted section so interaction failures are escalated instead of misattributed. A terminal multisect result is record-only: it preserves the train for the next rung or operator review instead of withdrawing members. The section width is an instance setting because wider rounds trade more concurrent focused grades for fewer wall-clock rounds.

Manual

Trigger: an operator starts a free-form run. Steps: manual. The operator's prompt is passed directly to the configured agent provider. Manual workflows capture transcript and diff information, but they do not push or open a PR by themselves.

CiFailure

Trigger: polling sees failed CI checks on an existing Syrus PR. Steps: prepare -> grade loop repaired by analyze_and_fix -> summarize_amend -> try(push). The agent receives the failing check payload, diagnoses the failure, commits a fix, and pushes the updated branch. CI repair runs the graders configured for the ci phase, including CI-only checks. If the remote PR branch advanced first, Syrus rebases before blaming the PR branch, and it avoids running many CI-failure Workflows with the same failing base at once. A rolling cap prevents endless CI-failure loops on the same Job.

Step Kinds

StepAgenticPurpose
prepareNoIf the repository is a fork, sync its default branch with upstream (merge, then push); then run deterministic setup from .syrus.yml or auto-detected lockfiles
implementYesMake the requested code change for Initial, Retry, cron, and direct work
adversarial_reviewYesIndependently review Initial and feedback workflow changes when .syrus.yml configures review rounds
visual_reviewYesDrive a preview in a browser and report visual findings when visual review is enabled
respondYesAddress PR review feedback on an existing branch
analyze_and_fixYesDiagnose failed CI checks and commit a fix
formatNoRun configured formatting commands inside the grade loop when present
generateNoRun configured generation commands inside the grade loop when present
landing_fixYesMake final merge-gate fixes before the landing graders run
summarizeYesCollect PR title/body/summary through MCP
summarize_amendYesProduce follow-up commit copy for PR feedback and CI-failure workflows
test_planYesCollect reviewer-facing test steps for Initial PR bodies
coverage_analyzeNoParse coverage artifacts, compute diff annotations, evaluate thresholds, and pre-render a PR comment body when coverage.pr_comment: true
coverage_pr_commentNoPost or update the coverage report comment on an existing PR (subsequent workflows); always a no-op when no coverage comment body was produced
pr_openNoPush the branch and open the pull request. For cross-fork Jobs (target repository differs from the working repository), opens a fork review PR (feature branch → fork default branch) as a staging artifact instead of the upstream PR directly. The upstream PR is created after the fork review PR is approved or merged.
pushNoPush commits to an existing PR branch, update the cost footer, and clean-rebase once if the remote branch advanced
push_agent_rebaseYesResolve a conflicting follow-up push rebase onto the current remote PR branch
push_after_rebaseNoPush a branch after the agentic follow-up rebase and grade loop
graderNoRun one configured repo grader. The UI groups grader setup, fanout, and aggregation under a single Grade row.
auto_rebaseNoTry a deterministic rebase before involving an agent
agent_rebaseYesResolve rebase conflicts with the agent
force_pushNoForce-push a rebased branch with an explicit --force-with-lease lease
auto_mergeNoRe-check GitHub merge gates and merge the approved PR
merge_train_assembleNoValidate an Epic train's locked members
merge_train_buildYesMerge the Epic's members into one integration branch, handing conflicts to the agent
merge_train_reconcileYesReconcile cross-Job inconsistencies on the integration branch before validation
merge_train_rebaseNoTry a deterministic integration-branch rebase when the base branch moves during landing
merge_train_agent_rebaseYesResolve a conflicting merge-train integration rebase before re-validation
merge_train_landNoAtomically merge the integration branch and close member PRs
merge_train_land_after_rebaseNoLand a merge train after base-moved recovery and validation
manualYesRun an operator-supplied prompt

Planned Step kinds include triage for a short readiness check before an expensive implementation run. That is a roadmap item, not a current template step.

Template Selection

Syrus chooses the template from the trigger kind:

Trigger kindTemplate
initialWorkflows::Initial
pr_commentWorkflows::PrFeedback
chat_feedbackWorkflows::ChatFeedback
ci_failureWorkflows::CiFailure
rebaseWorkflows::Rebase
stack_rebaseWorkflows::StackRebase
auto_mergeWorkflows::AutoMerge
merge_trainWorkflows::MergeTrain
retryWorkflows::Retry
base_refresh_retryWorkflows::BaseRefreshRetry
coding_handoffWorkflows::CodingHandoff
external_pr_ingestWorkflows::ExternalPrIngest
skillWorkflows::Skill
manualWorkflows::Manual

The template creates a Workflow row, creates each Step row in order, and wires next_step_id from each Step to its successor. The dispatcher starts the first Run, then advances the chain as Steps succeed.

Fork Review Flow

When a Job's target_repository differs from its working repository (cross-fork mode), the pr_open step creates a fork review PR on the fork rather than opening the upstream PR directly. This PR — feature branch against the fork's default branch — acts as a staging review artifact that the operator or a repository member must approve before the change reaches the upstream.

Approval signals (polling only — no webhooks):

SignalHow detected
GitHub review approval on the fork PRPollForkReviewPrJob reads /reviews and finds an APPROVED state
GitHub merge of the fork PRPollForkReviewPrJob finds merged: true (accidental merge, treated as implicit approval)

All signals are equivalent. On detection, ForkReviewApprover:

  1. Closes the fork review PR (skipped when already merged).
  2. Opens the upstream PR from the feature branch against target_repository's default branch.
  3. Updates the Job's pr_number and pr_repository_id so normal PR polling takes over.
  4. Records approval based on the repository's review policy:
  • self — records a GitHub review approval immediately; the Job becomes eligible for auto-merge.
  • two_person / final_say — creates a JobApproval for the fork PR approver (matched by GitHub handle to a Syrus user; falls back to job owner). Additional approvals on the upstream PR are also polled — PollPullRequestJob reads /reviews on the upstream PR and creates a JobApproval for each APPROVED reviewer it can match to a Syrus user. When approval_satisfied? becomes true, the Job is auto-approved for landing.

Feature branch lifetime — the feature branch is never deleted while the upstream PR is open. When the upstream PR is later closed or merged, PollPullRequestJob handles cleanup:

  • Merged — closes the Job as pr_merged; the fork branch is deleted.
  • Closed without merge — Syrus checks the branch against the PR base before deciding: commits that all have equivalents on the base mean the work landed anyway (pr_merged), a branch with nothing ahead of the base closes as no_changes, and a branch that still carries unmerged commits closes as pr_closed. The fork branch is deleted and an upstream_pr_closed notification is sent to the job owner.

PR feedback iteration history — each round of review feedback (a pr_comment or chat_feedback workflow) is stamped with a sequential iteration number in workflow artifacts (pr_feedback_iteration, pr_feedback_auto, pr_feedback_source_handle). The Job timeline (Jobs::Timeline) surfaces these as labeled "Feedback iteration N" events, showing whether each iteration was triggered automatically or confirmed by an operator, and who sent the triggering comment.

Next: Workflow Engine V3

The chain shapes above are data-defined per trigger kind, not a fixed linear pipeline — an implementing agent can already extend its own running workflow at runtime via the patch_workflow MCP tool (append-only: it can add a check like visual_review but can't remove a step or add one that publishes). The active engine-level initiative is Workflow Engine V3, which consolidates the separate vocabularies that chain composition, admission, failure classification, and reconciliation each use for the same failure event into one shared Problem code and remediation table.