Documentation
Features
Product feature reference for Jobs, Epics, schedules, chats, credentials, and repository automation.
Features
This page is the broad product-level reference. Use Concepts for the data model, Workflows for execution chains, Landing Code for merge behavior, Tests and Graders for validation, Previews and Visual Review for browser review, and Observability for debugging production behavior.
Jobs
A Job is the unit operators track in the UI. It represents one thread of work from a GitHub issue, scheduled task, direct prompt, or chat proposal.
Jobs show the repository, source prompt, state, priority, credential mode, agent provider, active Workflow, past Workflows, transcripts, captured diffs, PR link, attachments, dependencies, and logs. Operators can retry, cancel, run again, change priority, approve, or inspect the related PR. Clicking an uploaded attachment on the Job detail Attachments tab opens it in the same file preview popup chat uses for local file references: a Raw button opens the underlying file in a new tab, markdown files render as markdown with a Source toggle, and other text files get syntax highlighting. Google Doc attachments stay a plain external link; binary uploads (images, PDFs, Office documents) open the popup but show a "cannot be previewed" state with the Raw button still available. Dashboard Kanban boards for Jobs, Epics, and Workflows load cards per lane and show a Load more control whenever older cards exist beyond the loaded window. The Jobs and Epics dashboards also include a desktop-only Dependencies graph view for dependency relationships. For landed Jobs, Syrus can also record configured deployment stage progress from repository tags, such as staging, production, or public release. The Jobs dashboard has an optional Deployment column, hidden by default, that shows the furthest configured stage each Job has reached. Epic detail pages show those configured stages as columns in the child Jobs table, so operators can scan which landed Jobs have reached each stage. When a Job came from a chat proposal, or belongs to an Epic that came from a chat proposal, the Jobs UI links back to the originating chat message. A Job with no chat link at all yet — a bug report, or a Job ingested from an external PR — instead shows a "Chat about this" action; using it starts a new chat seeded with a durable reference to the Job so the agent responds against the current report, permanently attaches the Job to it (visible in that chat's Jobs list), and the Job page then links to that chat going forward. Only participants in an existing Job discussion can continue it; repository write access by itself does not allow someone to post into another user's private chat or start an agent turn there. Deleting the discussion removes that active link and allows a fresh Job discussion to be started. A chat's right-sidebar Jobs tab shows a "Proposed" section above its confirmed Job/Epic status cards, listing that chat's own pending Job and Epic proposals; each card links straight to the message where the proposal was posted, and the section updates live as proposals are created, confirmed, rejected, or withdrawn, so a proposal that scrolled out of view in a long chat is never effectively lost. The tab itself appears as soon as a chat has a pending proposal, even before anything is confirmed. While a chat agent is running, the message stream shows a compact activity indicator such as "running commands" or "searching code" when Syrus can derive that from the provider transcript, falling back to a generic working state when the provider format is unavailable or has drifted. If an agent provider hits a current user's usage or quota limit, Jobs that use that provider show an additive red triangle warning in dashboards, lists, and the Job header until usage is restored or the Job is retried/switched with another configured provider. Transient provider outages remain separate from quota exhaustion and keep the existing non-red circuit treatment. Repository throughput metrics are available per repository on the repository page's own "Throughput" tab and API at GET /api/v1/app/repositories/:id/throughput_metrics, reporting PR creation, output, landing, and review funnel windows with explicit confidence labels for sparse samples.
Job kinds:
| Kind | Source |
|---|---|
issue | An input-source issue selected by a configured source such as GitHub or Linear. |
cron | A scheduled task fire. |
direct | An operator-created prompt with no GitHub issue. |
Notifications
Signed-in operators see a bell in the app chrome with the current unread count. On desktop, the bell opens a recent-notifications panel with Job and PR outcomes such as failed Jobs, implemented work, addressed PR feedback, merged PRs, and completed Epics. Job notifications include the Job title; clicking the row opens the matching Job page, while a separate PR link opens the pull request. Both actions mark the notification read. The panel also supports marking all notifications read.
On mobile, the bell opens the full /notifications page with the same recent list and read behavior.
The same live event that updates the bell also dispatches a native OS notification via the browser's standard Notification API — clicking it opens the matching Job or pull request. Syrus never prompts for OS notification permission on page load; permission is requested lazily the first time you interact with the bell, and if it is denied or unsupported the in-app bell keeps working exactly as before. This works the same way in a plain browser tab and in the desktop app's app window. The desktop app's own independent native-notification dispatch is a fallback for when no app window is open (or it hasn't granted permission yet) — it stands down whenever the app window is already showing notifications itself, so you get exactly one native notification per event either way.
Primary Sidebar Navigation
The primary sidebar nav (Dashboard, Repositories, Schedules, Team, and any enabled plugin-provided items such as Spending) can be reordered by dragging an item to a new position. The chosen order is saved per user and follows that operator across devices and sessions. If a new item becomes visible later — a newly enabled plugin, or a feature-flag-gated item like Terminal — it's appended at the end of the list rather than dropped or inserted at a random position.
Reordering uses the same mouse-driven HTML5 drag-and-drop pattern as the recent chats list; there is currently no keyboard-only way to reorder items.
Terminal
When the terminal feature flag is enabled, the V2 sidebar shows a Terminal item with a live badge for running sessions. The /terminal page lists open sessions as tabs, lets operators start a session from recent Workflow workspaces or a scratch directory, and keeps the PTY alive while the browser navigates away. Returning to the page reconnects the xterm.js pane to the existing session; killing a tab ends that session. Creating a session is restricted to admins, and scratch paths are selected by Syrus rather than accepted from the request.
Coding Mode
Coding Mode is on by default (coding_mode feature flag). When a chat session is in coding mode, the chat agent gains tools to implement changes directly in a repository checkout and hand them off to Syrus automation. An admin can turn it off instance-wide from Admin → Features.
The coding sidebar includes a file tree, a diff browser, and a compact commit selector. Operators can inspect the live working tree at HEAD or choose a recent commit on the checkout branch to view file contents and that commit's diff.
A planning-mode chat with an attached repository gets a read-only version of the same Files tab — just the file tree and file content, with no Diff sub-tab or commit selector — once the chat has a local checkout of that repository (for example after the agent calls attach_repository, or automatically as part of a turn). This works whether or not Coding Mode is enabled instance-wide.
Before each Coding Mode turn, Syrus attempts to create or restore the writable checkout. When that checkout is first created or restored, repository preparation runs asynchronously on the chat worker so the agent can begin inspecting the code while setup is queued or running. The agent-visible context includes the checkout path, current branch/ref, default branch, and the latest known prep status or failure.
New chat-authored work starts on the repository default branch unless the agent intentionally checks out another ref. The submit_coding_changes chat tool creates a new direct Job from the active committed stack; after operator confirmation Syrus captures the submit base SHA, submitted HEAD SHA, changed files, and source branch, publishes the HEAD to an immutable syrus/chat-<chat_id>-handoff-<pending_action_id> branch, and dispatches the CodingHandoff workflow. After a successful capture and dispatch, Syrus leaves the chat checkout at the submitted HEAD so the operator and agent can continue iterating from that exact state. A later submit from the same continuous checkout uses the prior submitted HEAD as its stack base and links the new Job to the prior handoff Job when appropriate. The complete_implement_step chat tool signals that a coding session on an existing Job branch is complete and ready for review, grading, summarize, and PR open.
Existing Jobs can also be taken over from Coding Mode with the open_in_coding_mode chat tool, the same backend path the Job detail page uses for Open in Coding Mode. A coding chat can own only one active Job at a time: takeover is rejected if the chat already has another active coding Job or an active coding checkout for different work, and a Job already linked to a different chat cannot be claimed. Taken-over Jobs stay in the coding state while the chat owns implementation. Canceling the chat checkout detaches the Job in the same backend operation: Syrus discards the coding checkout, clears the chat link, and returns the taken-over Job to implemented. The cancel_coding_checkout chat tool exposes that detach primitive to agents; it does not preserve, migrate, or apply local code changes, so agents should save work to a local backup branch or tag first when they need to recover commits. Fresh chat-authored Coding Mode Jobs keep their existing cancel semantics rather than being converted into taken-over Jobs.
The reset_workspace chat tool is available for abandoned experiments or for starting a new unrelated task from the latest default branch: without confirmation it only reports the checkout path, current branch/ref, dirty state, commits ahead of the default branch, linked coding Job if any, valid handoff lanes, and prep status. This is a status/fresh-main reset tool, not the takeover detach path. When called with explicit discard confirmation, it resets the checkout to the repository default branch tip, clears uncommitted work and local-only commits, records the submit lineage as fresh-main, and queues preparation again. After that reset, the next submit_coding_changes starts a new independent handoff stack.
If an agent commits work before opening an existing Job in Coding Mode, the recovery path is to preserve the current commits locally, detach any conflicting active checkout, open the existing Job with open_in_coding_mode, apply the saved commits onto that Job branch, then hand off with complete_implement_step. submit_coding_changes is for new chat-authored work; it refuses branches that already belong to a Syrus Job because accepting one would create a new Job rather than update the existing one. An explicit branch names the branch that worker-side capture must later find in the selected repository's chat checkout; confirmation-time capture rejects a missing checkout, a different active branch, a dirty tree, or no committed changes. Normal submissions wait for operator confirmation. When the active Chat Goal has automatic Job submission enabled, Syrus queues that confirmation immediately instead.
Coding Mode chats also get a set of generic runtime_* tools for working with a live Runtime Session — a dev server, browser, or other running process attached to the chat's checkout — so the agent can start it, build or reload it, snapshot or inspect what's running, page through its logs, and tear it down when done: runtime_list_sessions, runtime_start, runtime_status, runtime_build_or_reload, runtime_launch, runtime_snapshot, runtime_inspect, and runtime_logs. Sending input to a Runtime Session (runtime_input, and the equivalent direct browser_click/browser_fill/browser_hover tools) requires the agent to first request a short-lived control lease with runtime_acquire_control and release it with runtime_release_control when finished, so agent-driven interaction never races an operator who is looking at the same session; runtime_capture_artifact files evidence (e.g. a screenshot) the same way, and runtime_stop tears the session down. Runtime providers include browser dev-server sessions and Android emulator sessions. Browser sessions navigate and inspect a running web app; Android emulator sessions create or reuse an AVD, install APKs through Gradle/ADB, launch apps, capture emulator PNG frames, inspect the UI Automator hierarchy, and page logcat. Both are watchable from Coding Mode's right sidebar in a Runtime tab (once the chat has started a session): session state, a generic visual frame viewer for providers that advertise screenshot/frame streams, latest captured screenshots as fallback, a scrolling log tail, and provider details. The panel also gives the operator a Take Control / Abort Agent Control button that immediately takes over input from the agent, lets the operator click, paste, or type into visual providers such as Browser or Android emulator while that input lease is active, and includes a button to capture a fresh screenshot on demand. Control leases are short-lived by design, but the panel renews an active operator lease automatically in the background, so staying on the tab keeps control without it silently expiring mid-task.
During a handoff, the Job remains linked to the originating chat so it stays visible in the chat Jobs tab and grader failures can route back to the same conversation. If a retry is pushed to a replacement branch, pass that branch to complete_implement_step; Syrus updates the Job before rerunning graders.
Typing ! as the first character of an empty message box in a Coding Mode or Local Mode chat switches the composer into a warning-styled command mode (shown every time, not just the first) and runs the rest of what you type as a shell command against the chat's checkout instead of sending a chat message — in Coding Mode that's the chat's own workspace; in Local Mode it runs on the operator's own machine over the same connection the agent's local tools already use. Backspacing back to an empty box returns to normal typing. Only one command runs at a time per chat; while it's running, Send is blocked for another ! command and a stop control lets you cancel it. Either way, the command and its output post to the chat and the agent takes a turn to look at the result.
The handoff tools create a pending action that the operator must confirm before Syrus dispatches any automation. reset_workspace runs immediately, but only performs destructive cleanup when the call explicitly confirms discard.
When the emergency_land feature flag is enabled, a Coding Mode chat linked to an active coding Job can request an emergency land. This is a repository-admin escape hatch for incidents: the pending confirmation uses warning styling and states that Syrus graders, adversarial review, and visual review will be skipped. If confirmed, Syrus opens the pull request if needed and merges it through GitHub directly. It does not push to the default branch itself, and it does not bypass GitHub branch protection or mergeability checks. Jobs landed this way close with closure_reason: "emergency_landed" and show the confirming operator, permission tier, and timestamp on the Job detail page.
If handoff graders fail, Syrus keeps repair inside the CodingHandoff workflow: a fresh workflow agent fixes the committed handoff branch and graders retry before the PR opens. The originating chat may receive status notifications, but it is not queued to repair the grader failure.
Local Mode
Local Mode is off by default for fresh installs (local_mode feature flag) — it opens an exec bridge onto the operator's own machine, so it's an explicit opt-in rather than a Labs-default-on toggle. It lets a chat agent read and write files, run commands, and inspect git state directly on an operator's own machine over a reverse WebSocket tunnel, instead of a server-side clone. Read-only tools run immediately; file writes and shell commands require an operator-confirmed pending action before Syrus sends them to the local daemon. Confirming a write or command approves that exact payload for the same chat session, but not for other chats. Switch a chat to Local mode from the chat mode selector, then run the paired syrus local --chat <chat_session_id> --token <auth_token> command shown in the chat's Local Mode banner from the target repository checkout. Once paired, the daemon can read/write files, run commands, and inspect git status/diff against that checkout on the operator's own machine; a session that drops past its heartbeat timeout needs a fresh pairing command from the chat UI to reconnect. An admin can turn it on instance-wide from Admin → Features.
Visual Review
Visual Review is on by default. Syrus adds a headless-browser QA pass to the implementation loop. After the agent implements a change, an independent reviewer agent boots its own preview of the running app, decides for itself whether the change is even visually testable (skipping invisible or backend-only diffs), and — if so — drives a real browser against it: clicking through the actual feature, not just loading the homepage. It captures screenshots as it goes so operators can see exactly what was tested, then records a verdict. If it finds a visible defect, the change goes back through another implementation pass automatically, the same way adversarial review's findings do; if it looks correct, or isn't visually testable, the loop exits and grading continues.
Operators can also trigger a visual review pass on demand from the Job detail page's "Run visual review" action — useful for a fresh look after implementation, or to cover a pass that was skipped or never configured.
A repository's .syrus.yml can opt a repository out, bound how many review rounds run, restrict visual review to specific changed files, and record seed notes (demo login, a record to look for) so the reviewer can reach an authenticated or populated view of the app instead of a blank one. For authenticated routes, reviewers use that seeded login in the same MCP browser session they drive for the review and submit screenshots from the current browser context, so artifacts keep the captured URL, path, title, viewport, and source. An unexpected sign-in redirect is treated as a blocker unless the sign-in screen is the intended surface under review.
Review Notes
The bundled cognitive_review plugin is disabled by default. When enabled, Syrus runs a best-effort agentic pass after implementation-style workflows, including confirmed Coding Mode handoffs, and asks it to flag changed diff ranges that deserve operator attention. Open notes appear in the Job review tab as warning-tinted ranges and plugin-owned side-panel cards.
Because this pass runs after the implementation PR is published, a missing submission tool or reviewer failure does not block the implementation. The Job stays implemented and its workflow detail shows a diagnostic warning with actions to retry only the Review Notes pass or explicitly acknowledge and skip it. A successful retry clears the warning without rerunning implementation or replacing the PR branch; skipped and recovered warnings retain their audit history.
Repositories can tune Review Notes in .syrus.yml with review_notes.criteria for high-signal interests and review_notes.low_signal for work the reviewer should usually skip. Root policy applies repo-wide; nested project policy is merged only when the final diff touches that project.
Generated and package-manager files stay discoverable in the review tab without dominating the first viewport: lockfiles and generated outputs declared in .syrus.yml are marked in the file header/list and their diff bodies are hidden until a reviewer chooses to reveal them.
The review tab also shows a PR-level review-note debt rollup for the selected diff version: total flagged ranges, open/unacknowledged notes, acknowledged notes, dismissed notes, and a zero-note state when the reviewer submitted no ranges. The diff also includes a slim right-side metric rail that stays visible while wide code scrolls horizontally: red marks lines covered by open review-note obligations, while blue marks changed or context lines with no open review-note obligation. A diff version with no notes and no submitted review marker is not presented as no debt. Acknowledged notes, discussed notes, and covered ranges with operator diff comments count as acknowledged. Unflagged changed lines are treated as having no PR-level review-note debt; repository-wide cognitive coverage or historical attention-debt metrics are separate concerns. Acknowledged notes remain visible inline for their selected diff version with a subdued resolved treatment, while dismissed notes are counted separately. Discussing an open or acknowledged note first asks for an initial operator prompt, then opens the Job discussion chat with Review Note context plus that prompt as a single user message. Cancelling the prompt sends nothing. Once a note has already been discussed, the Discuss action simply reopens the existing chat without sending another message or starting another agent turn. Operator review feedback still uses the normal diff comment path. Leaving a feedback reply from a Review Note creates a regular diff review comment anchored to the note range with Review Note provenance attached, so submitting review feedback preserves both the human comment and the note that prompted it.
Deploy
Repositories can configure a shell command Syrus runs to actually deploy the application — not a preview, a real deployment:
deploy:
mode: manual # or continuous — default manual
run: "bin/deploy" # required
allow_unapproved: false # default false
min_interval_minutes: 15 # optional, continuous-only
Once configured, a Job's detail page shows a Deploy button next to Preview, available whenever the Job has a resolvable revision (implemented, approved, landing, or already landed). Clicking it runs the configured command against that Job's code and shows the deploy's status — queued, running, succeeded, or failed — right there, with a link to the full run.
Deploying an unapproved Job is forbidden by default; a repository can opt out of that guard with deploy.allow_unapproved: true in .syrus.yml. Only one deploy can be in flight per Job at a time.
With deploy.mode: continuous, Syrus automatically deploys the repository's default branch every time a Job lands — no manual click needed. Deploys are limited to one in flight per repository at a time; if a merge lands while a deploy is already running, it doesn't pile up. min_interval_minutes optionally throttles how often auto-triggered deploys can happen — a burst of several merges inside the throttle window still results in exactly one deploy of the latest code once the window opens, never a dropped deploy.
Epics
Epics group related Jobs inside one repository. They are useful when a goal is larger than one PR but still needs visible sequencing.
An Epic has a board state: backlog, ready, in_progress, done, or archived. Child Jobs can be blocked until the Epic starts, and Epics can depend on other Epics or Jobs. Jobs can also wait on an Epic to complete. Landing is not a board state of its own — it's a Job-level state — but while at least one child Job is landing (typically all of them are, though not always), the dashboard and Epic detail page show landing as the Epic's apparent status in place of its underlying in_progress state, so an Epic about to merge doesn't look indistinguishable from one still mid-implementation. An Epic that has been created or approved does not run work by itself, and an empty Epic cannot start implementing until it has at least one child Job. The new-Epic form only creates the Epic shell; after child Jobs exist, the Start implementing action on the Epic detail page moves the Epic to in_progress and dispatches its ready child Jobs. Chat Epic proposal cards can still offer Create Epic & Start Implementing because those proposals include child Jobs up front. In linear Epics, children with same-Epic parents can keep implementing down the stack once the immediate parent has an implemented PR branch; approval and landing order still waits for the normal dependency gates. For a handful of older Epics whose child Jobs still branch or fan in (from before same-Epic dependencies were required to form a single chain), Syrus can prepare a combined execution base from approved dependency PR branches when they merge cleanly; otherwise the queued child shows an explicit fan-in base blocker with the dependency branches that need landing, linearizing, or conflict resolution. Syrus can mark an Epic ready when its dependencies are done and all child Jobs are confirmed, then mark it done automatically when all child Jobs close through merged PR or no-change outcomes. When every child Job is closed but at least one did not complete successfully, operators can use Mark as done or drag the Epic to Done instead of archiving it. Archiving an Epic immediately closes all of its child Jobs with reason epic_archived, cancelling any active workflows. This is irreversible through the archive action alone — reopen individual Jobs manually if needed. Product owners can create and refine backlog Epics, but developers own elaboration: product owners cannot move Epics to ready, in_progress, or done, and cannot add Jobs to Epics directly.
The Epic detail page shows both sides of the dependency graph: Epics this Epic depends on, and Epics that depend on it. Operators can add an Epic dependency by ID or remove an existing dependency from that page; Syrus rejects changes that would create a cycle. The page also includes a collapsible history section that records title and description changes with the actor, timestamp, and before/after text.
Repositories default new Epics to a linear child-Job dependency policy: child Jobs should form one ordered chain. Each Epic stores a concrete policy when it is created; linear is the only value that exists. Chat's propose_epic_with_jobs tool rejects a branching, fan-in, or disconnected child-Job graph immediately, with the offending child slugs in the error, before it even creates the proposal card — the operator never sees a card that would fail later. The same check runs again at confirmation time as a safety net. Adding more children to an Epic that already has Jobs is covered too: the new batch must chain onto the Epic's existing Jobs via jobs[].depends_on_job_ids, or it is rejected as a disconnected parallel branch rather than silently landing as one. The single-Job propose_job tool enforces the same rule when its epic_id targets a non-empty Epic: it requires depends_on_job_ids to name one of that Epic's existing Jobs.
When a Job proposal card targets an Epic, editing the card shows the target Epic as a removable pill so an operator can clear it before confirming, turning the proposal back into an epicless direct Job. The per-card Backlog route is intentionally scoped to standalone direct Job cards. Child Jobs inside grouped Epic proposal cards keep the normal Epic materialization path and are not individually routed to the Job backlog.
Epic reconciliation
When merge trains are enabled, approved child Jobs land atomically only after the Epic has released its children and all open siblings are approved. Children blocked by an upstream Epic dependency stay in the queue with a waiting for Epic to release reason instead of dispatching a train early. After Syrus builds the train's integration branch, it runs an agentic reconciliation pass on the recorded integrated SHA before prepare, graders, coverage, and landing. Older Epics with multiple approved leaves (predating the single-chain requirement) are assembled into that same combined branch first. A no-diff reconciliation continues normally; focused fixes are committed to the integration branch and still pass the normal gates before the Epic lands. Syrus records landing throughput metrics on workflow artifacts so operators can see cache skips, rerun reasons, wall-clock grader loop time, summed grader time, and required-grader failures while the throughput dashboard evolves.
Syrus no longer creates a standalone Reconciliation: ... child Job to review sibling consistency; that path, including its reconciliation_mode setting, has been removed. Existing historical reconciliation Jobs remain readable and can still close through the successful no_changes path when they produce no patch.
When reconciliation is a no-op, operators see that the merge train is continuing through normal gates. If the agent commits reconciliation fixes, those commits are validated on the integration branch before landing. If reconciliation fails, operators retry or inspect the merge-train workflow rather than filing a separate reconciliation Job.
Chats propose a new Epic together with its child Jobs in one card, including Epic-level dependencies on existing Epics or other chat Epic proposals. A confirmed Epic with zero child Jobs would implement nothing, so an Epic proposal must include at least one child Job up front; a single child Job is fine. Chat Epic proposals can reference each other by proposal slug, so operators can review the full plan first; Syrus wires the real Epic dependency once both proposal cards are confirmed. Confirming an Epic proposal targeting an existing Epic that is itself still empty is also rejected, for the same reason.
When a developer opens chat on a backlog Epic with no child Jobs, Syrus treats it as product-owner-authored planning input. The chat agent surfaces the original description, helps elaborate technical decisions, updates the Epic description first so version history preserves that elaboration step, and then proposes child Jobs against the existing Epic.
Atomic Landing Units: Epic Merge Trains and Job Bundling
Epic merge trains and epicless Job bundling are two scopes of the same underlying idea: several approved Jobs land together as one atomic integration branch instead of one at a time. A merge train scopes that unit to an Epic's children; Job bundling scopes it to same-priority Jobs that don't belong to an Epic at all. Both share the same all-or-nothing integration branch, build/reconcile/grade/land pipeline, and failure handling — Job bundling is not a separate or less mature mechanism, just a different way of grouping candidates for the same landing machinery.
Job bundling is gated by the epicless_job_bundling feature flag (on by default; operators can opt out from Admin → Features). Syrus only bundles Jobs that share the same priority tier: urgent Jobs bundle only with other urgent Jobs, never with medium or low-priority work, so urgent work still lands first. A single ready Job never waits around for a bundle — bundling only kicks in once at least two same-priority Jobs are ready to land together; a lone ready Job lands on its own right away. Externally filed pull requests are never bundled, since they land directly against the GitHub PR rather than through a Syrus-owned integration branch. When dependency-linked bundle candidates would otherwise be separated, Syrus keeps them together when the combined dependency-connected set fits within the configured merge-train size cap.
As with Epic merge trains, only the final integrated result that actually lands is graded — intermediate commits inside the integration branch are not individually validated. If a bundle fails, its Jobs return to approved for re-review (or automatically retry, for a transient failure), and Syrus waits out a cooldown before assembling a fresh bundle for the same repository so a genuinely stuck group of Jobs surfaces for an operator instead of retrying every queue tick.
The landing queue dashboard groups bundled Jobs together the same way it already groups an Epic's children, so operators can see at a glance which Jobs are landing as one unit.
Schedules
Schedules ship as a bundled plugin, enabled by default. Turning it off from Admin -> Plugins stops schedules firing and hides the Schedules pages; the schedules you configured are kept and return when it is re-enabled.
Scheduled tasks attach a recurring or one-shot prompt to a repository. Creating or changing one requires write access to that repository. Syrus also re-checks the task owner's write access before every fire and auto-pauses the task if that access has been removed. When a task fires, Syrus creates a normal cron Job and runs the standard issue-to-PR pipeline on a scheduled branch.
Supported schedule kinds:
| Kind | Meaning |
|---|---|
cron | Recurring schedule interpreted in UTC, limited to at most one fire per hour. |
one_shot | A single future fire time. |
Cron schedules can be entered as a five-field cron expression (0 9 * * 1) or as simple cadence text (Every Monday at 9:00 AM, Every day at 10 am, daily at 14:30). If typo-heavy or unusually phrased text doesn't match a supported cadence, Syrus can optionally hand it to an AI model to produce structured intent (frequency, day, time), which is then validated and canonicalized the same deterministic way as any other cadence — the model never writes the schedule directly, and firing itself never calls an AI model. Lists, ranges, and steps are supported in cron fields, including weekday schedules such as 0 9 * * 1-5; both 0 and 7 mean Sunday. If day-of-month and day-of-week are both restricted, the schedule fires when either field matches, following standard cron semantics.
The pr_pileup_policy controls what happens if the last scheduled PR is still open: skip, pile, or replace. Repeated failures can auto-pause a task until an operator fixes and resumes it.
A task can fire a named skill (see Skills below) with a set of parameters instead of a free-form prompt — the create/edit form lets an operator toggle between the two. A skill-based task still creates a cron Job and follows the same pileup and auto-pause behavior; only the source of the agent's instructions differs.
Reusable cron templates can likewise contain either a prompt or a named skill with schema-driven parameters. Skill templates are checked against the target repository when applied, so a repository-local skill that has changed or disappeared is reported before the scheduled task is created.
New tasks can be created from a repository's Schedules tab, or from the top-level Schedules page's "New scheduled task" button. The top-level entry point isn't scoped to a repository, so it asks the operator to pick one first; after that, the rest of the form is identical to the repository-scoped flow.
Chats
Chats are operator conversations that can start with or without repository context. New chats default to the operator's most recently used repository when one is available; the new-chat form shows that attachment in a repository dropdown, lets the operator choose any accessible repository, and includes a clear control plus a "No repository" option for starting without code context. A chat can attach repositories, Jobs, documents, memories, whiteboard state, and message-level image or PDF files. The chat can include one owner participant and additional member participants; every participant can open the session, post messages, receive live updates, and keep their own read/unread state. User messages record the sender so multi-person agent context can identify who spoke. A chat's group-ness is fixed at creation: the sidebar's New group chat picker starts a session with 2 or more humans from the outset, while an ordinary 1:1 chat can never gain a second participant later. In a group chat, the header lists every current participant and lets any of them add another (through the same picker, filtered to people not already in the chat). The owner may remove members; members may only remove themselves, which reads as "Leave" and takes them back out of the chat. The owner cannot be removed. The message list labels each human message with its sender's name so a multi-person transcript stays readable, and because the agent only replies to a group chat message that explicitly mentions @syrus, the composer shows a lightweight reminder of that requirement whenever more than one human is currently in the chat. Platform-origin sessions, such as future Telegram or Slack conversations, are found by platform plus participant membership and use the current speak_when_spoken_to trigger policy. The chat composer also supports active goals through /goal: operators can start a goal with /goal <objective>, edit it with /goal edit <objective>, pause or resume it, and stop it. Starting a new goal immediately wakes the chat when the agent is idle, and updating a paused goal through the start/upsert path resumes it with the same immediate continuation. Editing an already-active goal does not queue another turn. While active, goal-linked proposals, Jobs, and Epics wake the chat at meaningful boundaries so the agent can continue toward the goal; the agent can mark the goal completed or blocked, but pause/resume/stop remain operator controls. If a chat turn crashes and Syrus schedules an automatic retry, the retry status pill keeps the countdown visible and also offers a manual retry control so an operator can start the retry immediately. Automatic and manual retries wake the transcript with a Retry system message and run against the original operator prompt, so the chat does not appear to receive a duplicated user message. The chat agent can read selected repository context, propose Jobs, propose Epics, read user-visible Epics by id, list and update Epics, add or remove Epic dependencies, move Epics through their kanban states, schedule recurring work, inspect existing Jobs or PRs, explain stuck Jobs with structured dependency, Workflow, Run, landing, and PR evidence, approve or unapprove implemented Jobs, change Job priority, move Jobs into or out of Epics, drill through Job workflow and Run history progressively, search Jobs in the attached repository, read stored Job diffs, list and create the operator's tags, attach or remove tags on Jobs, manage repository documents after confirmation, search prior chat messages for the same user, read paginated chat transcripts, inspect Solid Queue health, update the chat's pinned context for future turns, and create proposal cards for the operator to confirm. New chats use a short interpreted title from the first prompt, with the repository name as the fallback. The app sidebar groups recent chats by repository by default, with sidebar settings for grouping by date, status, mode, chat type, or chat agent when those distinctions are available; each group can be collapsed when the operator wants to fold that section away. Rows show spinners for associated Jobs doing implementation work and fresh failed or PR-ready outcomes; collapsed groups roll those states up. Opening a chat clears its outcome glyphs for that participant, while unread messages remain indicated by the bold title. Hover or keyboard-focus the indicator cluster for the per-Job and Epic breakdown plus goal, pending-action, and scratch-pad details. Chat owners can pin or unpin a chat from the sidebar actions menu or through the chat agent, and rename it through the same menu (or the /rename slash command). Any participant can hide a chat from their sidebar and chat search, then restore it from the Hidden chats section in user settings. Hiding is personal: in a group chat it changes only the current participant's sidebar, search, and agent chat listings. The sidebar actions menu also lets the owner delete a chat after a confirmation dialog, and only the owner may clear its shared history: deletion marks the conversation as deleted (recording who deleted it and when) and removes it from the sidebar, chat search, and the chat agent's own tools, while leaving the underlying conversation, messages, attachments, proposals, and whiteboard in place for audit. The chat's search-index entries and its workspace on disk are still cleaned up. If an agent turn or workspace preparation is running, deletion requests that it stop and waits for termination before cleaning up local artifacts. Major operational events, including Job notifications, Epic completion, main-branch health changes, and new Agent Insight suggestions, are first stored as scoped chat event records. Scoped events can run through an isolated disposable evaluator before waking the live chat: Syrus clones the persisted transcript, uses the full context when it fits and otherwise caps to the latest 10,000 messages plus a byte budget, gives the evaluator read-only tools, stores its structured no_op/respond/act decision, and discards the temporary provider session without touching the live chat session. The scoped event path wakes ordinary chat threads only for work that originated in that thread through confirmed proposal lineage: Jobs, Epics, related Workflows/Runs, and pull requests that map back to those Jobs. Unrelated Job and Epic events stay out of ordinary chats. no_op decisions stay silent in the transcript, while respond and act decisions create an immediate wakeup for the real chat agent with the structured event, evaluator decision, and handoff prompt. The admin overview includes operator/debug visibility for this path: recent scoped events, 24-hour no_op/respond/act counts, evaluator state counts, and recent failure reasons. Failed evaluator events can be retried without duplicating already delivered visible wakeups. In the V2 layout, the sidebar search field opens a dedicated search page where operators can search Jobs, Epics, chat messages, and enabled plugin-contributed sources such as Tests and Design Docs from one ranked result list, then narrow results to a single type. Search terms use the query= URL parameter and Google-style matching: unquoted words are independent required terms, and quoted words search for an exact phrase. Matching chat messages are grouped by conversation, with the strongest snippet shown first and additional matches expandable inline. The global results page also supports the same predicate FilterBar used on dashboard lists: combined results expose common repository and timestamp filters, while single-type views expose the relevant Job, Epic, Test, or Design Doc filters. Design Doc results include their DOC-N slug, state, repository context, owner, current version, updated timestamp, and link directly to the Design Doc editor. These filters keep the relevance order intact and are encoded in the q= URL parameter; older plain-text q= search links still open as text searches when no query= parameter is present. Job and Epic results show a copyable JOB-N / EPIC-N slug that opens the same preview popup used elsewhere in the app on hover; workflow references use the same interaction for WF-N and preview their type, state, and key ordered steps. Plugin slugs such as DOC-N use their plugin-provided preview cards. The older chat search page remains available for chat-specific repository, Epic, Job, and attachment filters. Operators can also share a chat with teammates on the same Syrus instance: the /share slash command copies a stable link to a read-only transcript view that requires normal Syrus sign-in and does not expose compose controls, pending actions, or agent controls. Chat agents can also immediately withdraw an unconfirmed action they proposed in error. Withdrawal remains scoped to the current conversation and operator; grouped actions must still be rejected together from their group card. At the end of a turn, the chat agent can suggest the operator's likely next message; the suggestion appears as muted ghost text in the empty composer with an accept button. Tapping the button or pressing Tab fills the composer with the suggestion, typing anything hides it, and Escape dismisses it. Suggestions clear automatically when the operator sends a message or a new turn starts. The chat composer accepts image and PDF attachments through the plus button and sends them with the next message. Before sending, operators can click an image thumbnail in the composer to mark it up with basic shapes, arrows, freehand strokes, text, and undo; Syrus flattens the result into a PNG attachment. Image attachments render as inline thumbnails in the transcript and open in a full-size preview. The chat workspace also includes a Media tab that gathers image attachments into a downloadable gallery and lists saved whiteboard snapshots with element counts, relative timestamps, and a Load action that merges the snapshot back onto the current canvas after preserving existing work. Every image in the gallery — including Runtime Session screenshots captured in Coding Mode — has an Attach to message action, available both on the thumbnail and in its full-size preview, that pulls the image into the composer through the same attachment mechanism as a pasted screenshot; further discussion or markup happens in chat once it's attached. When the chat agent lists available media, its result card shows image thumbnails, labeled whiteboard snapshot tiles, links to each available source, and a full preview that remains usable on small screens. When chat speech-to-text is enabled, the composer microphone uses the best available dictation mode reported by the backend: an enabled and healthy speech-to-text provider such as the bundled whisper_stt plugin receives ordered chunks and revises the visible interim transcript while you speak. Final text is committed once without disturbing text already in the composer. The browser retains the complete recording and automatically retries backend batch transcription if streaming fails; disabled or unavailable providers use the browser's built-in speech recognition where supported, with the same interim replacement behavior. Empty or extremely short recordings are not uploaded. A final batch request is retried once only when the backend is unavailable or times out; input errors are not retried, and existing or progressive composer text remains intact when transcription fails. Once the current chat session has a pending or confirmed proposal, or a Job directly linked to it, a Jobs tab appears in the workspace panel. A "Proposed" section at the top lists this chat's own pending Job and Epic proposals, each linking back to the message where it was posted. Operators can select proposals individually or all at once, then send the selection to the backlog, start implementation, or reject it; Epic proposals can be implemented or rejected but cannot be routed to the backlog. Below that, confirmed proposals are grouped into their respective Epics (collapsible, with a done/total progress pill), with remaining standalone Jobs shown as a flat list. Each confirmed-Job card displays the Job state, the active workflow step or PR link, and a red blocker banner when operator action is required (awaiting review, landing failed, or a failed dependency). Clicking a card navigates to the Job detail page. The feed updates in real time when any proposal or Job originating from this chat session changes. PDFs are passed to the agent without an inline preview. Clearing a non-empty canvas automatically saves the previous scene first.
Chats do not silently materialize work just because the assistant suggested it. Proposal tools create cards; confirmation creates the real Job, Epic, GitHub issue, or scheduled task. Before confirmation, operators can edit a proposal card's title, body, and dependencies directly. Epic bundle cards let operators edit the top-level Epic proposal, and each proposed child Job in the bundle has its own editor. The proposal slug remains stable so dependency references do not break. Job proposal cards show the requested priority. A proposal can select urgent, high, medium, or low (medium by default), including a separate priority for each child in an Epic bundle. Confirmation applies it before work starts, so the first scheduling decision already observes the selected priority. Standalone direct Job proposal cards also show a route before confirmation: Start normally by default, or Backlog when the card is explicitly routed there. Operators can change that route on the single direct Job card. Confirming a Backlog-routed direct Job creates the owned Job, links it back to the chat, attaches any selected media, and preserves dependency wiring, but it does not start the initial workflow until the Job is later released from backlog. When a product owner confirms proposals, Syrus accepts a new Epic proposed together with its own required initial child Job(s) and accepts non-Epic Jobs, but rejects any proposal that would add further Jobs to an already-existing Epic until a developer claims and elaborates that Epic. Proposal cards can also declare dependency edges up front, including Jobs blocked on existing Epics, Epics blocked on existing Jobs, and proposed Jobs blocked on specific Job proposals in other cards from the same chat session. Proposal-slug Job dependencies are temporary while a proposal cascade is being confirmed: Syrus promotes them to concrete Job dependencies when the referenced proposal creates a Job, and removes them if the referenced proposal is rejected, withdrawn, deleted, or confirmed without a Job so real Jobs do not stay queued behind stale proposal slugs.
When a proposal is created, edited, or confirmed, Syrus rejects dependency targets that are already terminal and cannot satisfy the normal dependency gate, such as a Job closed as cancelled or an archived Epic. Successfully closed dependencies such as pr_merged, external_pr_merged, pr_approved, and no_changes remain valid and do not block startup. Proposal dependencies can also carry explicit wait modes. The default remains successful completion, but a proposal may declare a cleanup-style wait-until-closed gate or a deployment-stage gate for an existing upstream Job whose repository declares that stage in deployment_stages. Syrus validates those stage names before the proposal is confirmed so a Job is not created with an impossible deployment gate. Manual dependency edits can also mark a cleanup or teardown gate as wait-until-closed. Those edges start once the target reaches any terminal close; normal implementation dependencies continue to require successful completion. Actions that need explicit approval, such as canceling, closing a Job successfully as no_changes, retrying, reopening, polling feedback, checking mergeability, delegating GitHub issues, firing scheduled tasks, changing repository documents, or pausing and resuming the landing queue, also render as inline confirmation cards in the message stream so operators can review the target before confirming or rejecting them. Archiving an Epic also uses a confirmation card because the archive cancels and closes every still-open child Job under that Epic. When the agent acts on several Jobs at once with reopening, retrying, cancelling, closing successfully, forcing a landing recheck, killing a process, approving, or unapproving, Syrus renders one grouped card with a single Confirm all/Reject all control instead of a separate card per Job, and reports each Job's individual outcome once confirmed. A higher-risk set of admin repair actions -- force-failing a Job, marking a CI repair a no-op, retrying a failed step, reconciling Job state in auto mode, clearing or repairing provider-circuit evidence, forcing a rebase, and pausing or resuming a user's scheduling -- can also be batched the same way, but only once the agent supplies one shared justification explaining the root cause behind the whole batch; that justification is shown on the grouped confirmation card and kept in the audit trail alongside each item's outcome. Successful Job close is separate from cancellation: close_job_successfully records a successful closure reason such as no_changes, which satisfies dependencies and Epic progress, while cancel_job remains non-successful. When the Job has a tracked PR, Syrus can post the supplied explanation and close the PR after confirmation; any GitHub cleanup failure is reported in chat. On the Job detail page, implementation retries can also be enqueued with any other agent provider the operator has configured. That retry-with-provider choice is one-shot for the retry workflow. The Job detail page also has a provider selector for future workflows: leave it on Default to resolve the current repository/user provider each time a new workflow is created, or choose a concrete enabled provider for later feedback, rebase, and retry workflows on that Job. The same page exposes job-specific routing rules and a resolved provider explanation, including the matched scope or rule, so operators can see why future work will use a provider before starting another workflow. Rules can be enabled inline, duplicated, edited, deleted, and reordered without disabled rules losing their ordered position. Disabled rules remain visible but muted, are skipped during evaluation, and the first enabled matching rule wins. A rule can route to ordered provider candidates or hold work with a reason and manual, condition-based, or timed resumption policy. Job pages show active hold status and the next scheduled wake when applicable. A matching hold action stops first-match evaluation and leaves the workflow queued without launching a Run or treating the decision as provider failure. Holds are durable and retain their matched scope, reason, condition evidence, and resumption policy. They can resume when their conditions clear, at a configured time, or after an audited admin release or override; every wake evaluates the ordered routing rules again. Because routing admission happens before Run creation, a hold never interrupts an already-running provider process. Usage-based rules can match provider state or remaining-capacity thresholds, and fresh provider evidence wakes matching holds immediately rather than waiting for their safety timer. Existing workflow pins are not rewritten. Chat agents can inspect those same user, repository, and Job rule sets, explain the effective first-match decision, and prepare atomic rule changes for operator confirmation. Every change requires the latest rule-set revision, displays a before/after summary, and is rechecked when confirmed so a stale request cannot overwrite newer edits. Dashboard and repository bulk retry choose a narrower recovery path first: resume a failed agentic step when possible, retry the failed step while its workspace remains available, retry or rebuild landing workflows for landing failures, and use a full implementation retry only as the fallback. Bulk retry responses include action and skipped-reason counts, including active Runs, already-current passing PRs, duplicate retry workflows, and open provider circuits. When a short synthesis step cannot resume the provider session, Syrus can retry it from durable Job, summary, and diff context instead of discarding the completed implementation. Proposal cards show dependency status before the title: either dependency proposal links with confirmed or pending badges, resolved Epic proposal links, or an explicit no-dependencies note. Grouped Epic cards also label child Job dependencies as sibling or cross-card references, and resolved cross-card Job references link to the created Job. Confirmed and discarded proposal and action cards are also written back into the chat transcript so the next assistant turn can see the created Job, Epic, or GitHub issue identifiers without asking the operator to repeat them. When an operator confirms or rejects a proposal, Syrus immediately starts a system-generated chat turn that tells the agent the outcome.
After discussing changes with an operator, the chat agent can propose structured feedback on an implemented or approved Job. Operator confirmation creates a chat_feedback Workflow, runs the agent on the existing PR branch, and unapproves approved Jobs so the follow-up change returns to review before landing. After a successful feedback workflow, Syrus asks the agent whether the feedback changed the Job's effective intent; when it did, Syrus refreshes the top-level Job summary, test plan, and managed PR title/body separately from the follow-up commit message. If the Job is still queued or running, Syrus stores the feedback as a waiting pending action and promotes it for operator confirmation when the Job reaches review. Chat feedback can include a whiteboard snapshot or pasted screenshot alongside the written description — useful for pointing at a visible bug rather than describing it in prose. The attached media becomes a Job attachment the implementing agent sees automatically on the very next feedback round. When the chat agent lists available media, the transcript shows a compact thumbnail gallery with raw tool details still available for debugging. App surfaces, including the Job detail page, can also submit feedback directly for implemented or failed Jobs, bypassing the chat pending-action confirmation while creating the same chat_feedback Workflow. If a feedback workflow fails before the comment is addressed, the source PR comment remains on the Job detail pending-feedback panel with the last failure reason and a retry action. Retrying reuses the stored comment and workflow artifacts; the operator does not need to add another GitHub comment.
Attached repository checkouts are read-only for the chat agent. Chat can run through Claude, Codex, Antigravity, or Muse when the corresponding plugin and user credentials are configured. Syrus stores a concrete provider on each chat when it is created, seeded from the operator's current chat provider setting and then the default agent provider, so later user-default changes do not silently move the conversation between providers. When multiple providers are configured, a new chat's composer shows an inline provider dropdown next to the mode/model controls until the first message is sent; picking a different provider there switches the still-unstarted chat immediately, with no separate dialog. Once a chat is underway, settings shows the stored provider without offering an in-place switch. Branched chats preserve the stored provider choice, and stored agent sessions only resume when the next turn uses the same provider. Chat may read, search, list, and refresh checkouts for context, but code changes must be drafted as proposals for operator confirmation. Claude chat turns also deny Claude's file-editing tools (Write, Edit, MultiEdit, and NotebookEdit) so the planning surface does not patch repository files directly.
For Claude-backed chats, the composer toolbar exposes a per-chat Effort selector (None / Medium / High). Selecting a non-None level passes the corresponding --effort flag to Claude, which controls extended thinking depth for every turn in the session. The setting persists with the chat so operators can dial reasoning intensity for complex planning sessions without affecting other chats.
When the chat agent is already running, operators can queue follow-up messages instead of waiting for the turn to finish. Queued messages remain editable and deletable until Syrus promotes the next one into the transcript and starts the following turn.
The chat agent can schedule one-shot wakeups for the current chat. At the requested time, Syrus starts another visible chat turn with the stored prompt, so the wakeup and any follow-up action remain part of the transcript. The agent can schedule short relative wakeups with delay_minutes, or schedule a specific absolute UTC time with fire_at in ISO 8601 form for calendar dates and longer reminders. It can list pending wakeups for the current chat and cancel any that are no longer needed.
The chat agent can also send a message to another one of the same operator's chat sessions, opening a bounded, visibly-marked message thread and waking the other chat's turn — useful for sharing context or coordinating work between chats running different repositories or different chat providers/models. A new thread can only be opened as a direct result of the operator's own message in the current chat, so an automated turn (a wakeup, a scheduled message, and so on) can never spontaneously start one on its own. Confirming a grouped chat spawn is also explicit authorization: Syrus pre-opens a higher-hop coordination thread between the spawning chat and each chat it creates. Once a thread is open, either side can reply until the exchange reaches its hop cap, at which point Syrus auto-closes it and posts a visible notice to both chats. This is same-operator-only for now — a chat can only message another chat it owns.
Operators can also schedule their own follow-up message directly from the composer with /schedule [time] [message]. When both arguments are present, Syrus stores the message immediately and sends it at the parsed time; otherwise the composer opens a small Schedule Message dialog. Supported time forms include 30m, 2h, 1d, in 1 hour, tomorrow 9am, and HH:MM.
The chat agent can also ask a blocking inline question when it needs an operator decision before continuing. Syrus shows the question above the compose area, renders multiple-choice options as buttons when provided, and otherwise accepts a short free-form answer.
The chat composer recognizes leading slash commands. Typing / opens an autocomplete palette with system commands handled in the browser and skill commands that are sent through the normal chat message path for the agent to interpret. System commands can rename the current chat, clear chat history after an inline confirmation, start a fresh chat attached to the same repository, branch the current transcript into a new chat session, open bookmarks, attach another repository by owner/repo, and open chat settings without sending a message to the agent. /pin pins the current chat to the top of the sidebar, and switches to unpinning when the chat is already pinned. /report opens a small form that files a GitHub issue against the configured Syrus report repository with the current chat as context. The /share system command copies a same-instance read-only chat link to the clipboard, and /schedule [time] [message] stores a one-shot operator message for later. Navigation system commands include /jobs [filter], /job [id], /epic [id], /prs, /issues, /proposals, and /review [id]; ID commands without an ID open a picker scoped to the attached repository when possible. /review opens the selected Job's pull request. /bookmark <label> stores a chat bookmark. Job action commands such as /cancel [id], /retry [id], /approve [id], /discard, and /clear-canvas show an inline confirmation before they mutate anything. /approve accepts JOB-123, job-123, or 123, opens a picker of implemented Jobs when no ID is provided, and approves the selected Job for landing after confirmation. Skill commands such as /canvas, /feedback, and /propose are sent through the normal chat message path for the agent to interpret. /propose starts a guided wizard: the agent asks for a Job title, description, and optional Epic, then creates a proposal card for operator confirmation.
A chat attached to a repository also gets one slash command per skill available to that repository (see Skills below) — the list is computed for that repository, so it varies by repo and picks up repository-local skills automatically. Typing /skill-name key=value ... runs the skill immediately, with no confirmation card, reusing the chat's own Coding Mode session rather than a separate execution path — a skill that needs to run commands requires Coding Mode to be enabled for that chat, and Syrus says so plainly if it isn't. Which skill definition ran (built-in or repository override) shows up in the chat's tool-call trace. If the skill changes code, the same Coding Mode handoff confirmation as any other Coding Mode change still applies before a Job or pull request is created; a skill that only reports back (no code changes) simply posts its results in the thread.
Operators (and the agent) can pin individual user and assistant messages independently of bookmarks. Hovering a message bubble reveals a pin toggle alongside the copy and bookmark controls; pin state is shared across every chat participant, the same as bookmarks. The 3 most-recently-pinned messages show as truncated single-line previews in a bar at the top of the chat, newest first, and clicking one scrolls to and highlights that message. When more than 3 messages are pinned, a "view all" link opens a "Pinned" tab in the chat's right-side workspace panel with every pinned message, its sender, and a relative timestamp — clicking a row there navigates the same way. Pin and unpin events update the bar and panel live for everyone in the chat.
Chat transcripts also surface MCP sidecar health. Syrus distinguishes available, pending, and unavailable chat tools so operators can tell when proposal, schedule, bookmark, or whiteboard persistence is not ready and retry the turn or inspect worker logs instead of chasing blind retries. Chat agents can also pin or unpin visible chat sessions the current operator owns, separate from pinned-message previews and pinned context notes. They can request up to ten new chat threads at once, each with an initial prompt and optional title, provider, and accessible repository. Syrus shows one confirmation for the whole batch and, after confirmation, reports the new CHAT-<id> values and pre-opened bridge thread IDs in the originating thread. The spawning agent can immediately send follow-up instructions on those threads without duplicating each new chat's initial prompt or waiting for another operator message. Cross-chat threads allow 100 messages by default, while the send_chat_message tool can request a smaller positive cap when opening one. Workflow and chat MCP calls are also recorded as durable usage rows so the admin API can report top tools, unused advertised tools, error rates, and chat-versus-workflow usage over a time window without reparsing transcripts. SQL-backed chat list and transcript tools cap one-based page numbers at 10,000 before calculating database offsets; ordinary pages beyond the result set return an empty page. Provider usage-limit and quota-exhaustion failures are surfaced as prominent chat banners. When Syrus can identify the provider/model, it halts automation for that scope immediately; when the model is unclear, it fails closed at the provider level and preserves the underlying provider error in the banner. Chats that currently use the exhausted provider also show a red warning marker in the sidebar, chat header, and provider settings until usage resets or the operator switches the chat to another configured provider.
Agents can persist structured memories such as user preferences, project facts, feedback, references, and decisions. Agent memory ships as a plugin, enabled by default, and is the store the memory tools and the memory section of agent prompts read from — disable it from Admin → Plugins and prompts simply carry no memory, or replace it with a different store entirely. Workflow reviewer agents can read, search, and list memories for context, but they cannot write or delete durable memories; review runs are intentionally read-only. Memories are private to their owner by default; repository-scoped memories can be published to make them visible to other operators attached to that repository. The Memories settings panel lists, filters, edits, publishes, unpublishes, and deletes memories, with admins able to manage memories across users. Every create, edit, and delete is recorded in an append-only audit trail; rows with a history beyond their initial creation show a changed indicator that opens a history modal with each event's before/after content, kind, confidence, actor, and timestamp. A deleted-memories view (admin-or-owner scoped, same as the rest of the panel) lists soft-deleted memories alongside who deleted them and when. Admin chat agents can call admin_read_memory_audit_history to read the same audit trail for any memory, including deleted ones.
Admin users also get chat tools for operational diagnostics: overview, stuck Jobs, stuck explanations, queue tabs, spawned processes, Runs, users, running instance versions, and worker host health. The stuck views and explain_stuck_job tool use the work-engine reconciler's classifications and repair plans, so they show whether Syrus is waiting on capacity, dependencies, main health, or rate limits; can safely auto-repair; already repaired the issue; or needs operator action. Worker health includes live per-worker warnings, normalized worker capability advertisements, capability probe diagnostics, recent CPU/memory/disk/IO samples, and compact trend windows by hostname. It also exposes bounded one-minute history buckets for recent per-pod inspection, so operators and agents can inspect pod pressure without an external metrics system. Run and Job detail payloads also include compact worker-health correlations, and insight agents can compare retained host pressure across Runs when filing suggestions about repeated grader or step behavior. Grader and preflight grader Runs include command spans for common phases such as dependency checks, installs, database preparation, test suites, and frontend builds, so slow setup can be separated from slow tests. External commands use one tracked process lifecycle whether Syrus captures a short command, streams a long-running command, connects a command pipeline, or manages a background service. Each child remains visible as a spawned process with its owning Job or Workflow, redacted command, heartbeat, timeout, operator-cancellation state, exit status, and final outcome. The same worker health data is visible from the Workers tab as a per-host chart dashboard with current status, quick/custom time ranges, and recent trends for CPU, load, memory, disk, CPU pressure, and IO pressure. Only fresh heartbeat rows appear in the primary current-worker tables; hosts that stopped heartbeating remain chartable while their samples are in the selected range and are labeled historical, and stale Solid Queue process rows are labeled stale in the process inventory. Workflow admission control is off by default, so a fresh single-host install needs no capacity tuning. Admins of busy shared, multi-user instances can opt in from Admin Settings. The master switch covers workflow-level soft pressure and in-flight limits, host-local yielding, reservations and staggering, the global agent-run cap, and per-user fair share. Hard worker memory/disk and critical local-pressure protection remain active, as do provider availability, manual pauses, dependencies, and landing correctness gates. Syrus audits switch changes and wakes delayed Workflows when it changes. When the switch is enabled, admission control still keeps a minimum-progress floor of one controlled workflow per healthy worker while running agentic work is below that floor. Soft pressure and conservative predictions can slow the queue, but they cannot starve all landing or merge progress; hard worker memory/disk exhaustion still stops starts. Admission decisions record a telemetry_state of present, stale, or absent worker host telemetry alongside the pressure numbers, so a monitoring gap (no recent host samples) is distinguishable from workers that are genuinely busy or exhausted, instead of both looking like a 0% pressure reading. When pressure is isolated to one worker storage key while other workers remain healthy, the same diagnostics call that out as localized worker pressure, listing the unhealthy storage key and hostnames separately from healthy alternatives. That keeps a single bad worker from looking like whole-pool saturation while the per-host admission guard still prevents work from starting on the critical worker. When a Job is blocked on admission control, its Job detail page shows an operator-facing pressure breakdown directly — which dimension tripped, its current value against the threshold that tripped it, and whether the reading behind it was actually measured or backed by absent/stale telemetry — without requiring admin access. Admins additionally see a link out to the full /admin/resource_admission diagnostics page from the same panel. Operators can also manually pause any Job from the dashboard, including in bulk. Manual pause lets the current workflow step finish and then prevents Syrus from starting the next step or workflow for that Job. The Job remains paused until an operator unpauses it; unpause returns it to normal scheduling, where dependency checks, provider availability, landing gates, and admission control still apply. Syrus also stops runaway Jobs automatically: 10 consecutive failed Workflows close the Job as too_many_failed_workflows, and 50 total Workflows close it as too_many_workflows. This prevents repeated queue wakeups or retries from creating hundreds of attempts for one Job. State-changing admin tools, such as pausing runs, killing a process, clearing the GitHub cache, or refreshing installations, create pending actions and wait for operator confirmation before applying. Non-admin chats do not advertise those tools, and each admin tool repeats the admin check when it runs. When several related actions are proposed together as a batch, chat renders them as a single pending-action-group card instead of one card per action: the shared action name and target count, an expandable list of the affected targets, and one Confirm all/Reject all control. Confirming applies every target independently and shows which ones succeeded and which failed and why; rejecting discards the whole batch without applying any of it. Admins can also force-fail an open stuck Job from the stuck Jobs page, admin API, or confirmed chat tool so the normal Retry path becomes available without closing the Job. The stuck Jobs page and admin stuck APIs paginate results in 50-item pages, while the admin overview shows the first page and a total stuck count. The admin_stuck_jobs chat tool uses the same 50-item default, accepts bounded page and per inputs, and reports totals plus next/previous-page availability so large watchlists stay safe for the MCP transport. Admin queue filters can be saved as smart folders from the queue sidebar, then renamed or deleted inline from the saved-folder list so repeated operational views stay available beside the built-in queue folders. The Processes page works the same way, filtering the subprocess inventory by state (running or finished), by outcome for finished processes (failed, timed out, operator killed, and the rest), by owning user, and by kind, host, or Run, with built-in Running, Stale, and Recently failed folders.
Admins can also toggle boolean feature flags from /admin/features when the instance declares features in config/features.yml. The page groups declared flags by category, shows the slug and description for each flag, and hides the admin navigation item entirely when no flags are declared. Flags marked beta or experimental remain visible and searchable, but an admin must enable instance beta mode before turning them on. Beta mode does not enable any feature by itself; it only permits explicit opt-in to beta or experimental capabilities.
The performance_logging operations flag records structured slow-request, slow-SQL, slow application-phase, and selected browser route-load events for production debugging. Slow request events include request/user context, trigger reasons, SQL counters, and top SQL fingerprints for that request; requests that are fast wall-clock but cross the request SQL-count or SQL-duration thresholds are retained too, so many-query endpoints do not disappear from the performance view. Phase events cover expensive dashboard, chat, repository, job detail, bootstrap, spending, and admin payload builders, with total and self SQL counters plus top SQL fingerprints. Browser traces cover dashboard loads and include only structural timing data: route path, browser-observed duration until current rows render, document visibility state, row counts, and backend request IDs/durations/statuses for dashboard API calls. They do not include row content, titles, prompts, or free-form filter text; route paths are sanitized before logging. Event-loop lag sampling only reports while the tab is visible, skips the interval spanning a return from the background, and discards implausibly long gaps: a timer that was throttled in a background tab or paused across system sleep measures the pause, not the page, and would otherwise swamp real jank.
Beyond those passive observers, app/frontend/lib/performanceMarkers.ts is a generic frontend performance marker API frontend code can use to place explicit timing markers, similar in spirit to the backend's PerformanceLogging.phase. It offers measureSync/measureAsync for timing a block of work, startMarker/endMarker for spans that don't fit one function call (e.g. a UI menu's open-to-render latency), recordCount for count-based context without timing anything, and the useMarkedRender hook for a React component's render/commit or render/paint boundary. Every marker reuses the same browser-trace envelope and ingestion path as the passive observers, so it shows up in the same Admin Performance browser-trace groupings without a separate diagnostics surface. Markers support a per-call thresholdMs (drop anything faster), sampleRate (probabilistic keep), and maxPerSession (a hard cap per marker name per page session) so a marker placed in hot UI code cannot flood production logs; events queue in memory and flush together (debounced, size-capped, or on tab hide via navigator.sendBeacon) instead of one request per marker. The diff review surface (ReviewWorkspace/ReviewableDiff) is instrumented with diff_review.fetch_source_diff, diff_review.parse_diff, diff_review.initial_render, diff_review.syntax_highlight, diff_review.viewport_render, diff_review.comment_threads_render, diff_review.files_menu_open, and diff_review.anchor_scroll markers, with metadata such as file/row counts, hunk counts, syntax token span counts, and thread counts, so a slow review session can be attributed to parsing, rendering, highlighting, comments, or navigation instead of just "the page was slow." The admin performance endpoint returns recent raw events plus grouped summaries for slow routes, phases, browser traces, and SQL fingerprints. Events are stamped with the running app revision, and the admin view defaults to the current revision so stale pre-deploy timings do not hide whether a new deploy helped; admins can switch to all revisions when investigating rolling-deploy overlap. The in-app diagnostics buffer uses the observability event sink, defaults to 2,000 recent performance events per process, and persists events for 24 hours; structured log retention follows the deployment's log sink policy. When the bundled syrus_dev plugin is enabled, Admin → Performance and the admin performance API expose the same diagnostics payload. The slow-request table can drill into retained request events and show matching slow phases plus the bounded top SQL fingerprints captured for both the request and each phase; these samples are diagnostic context, not an exhaustive query log. The SQL tab can run EXPLAIN for captured slow-SQL samples and shows a visual plan, raw table rows, raw JSON, and normalized SQL. EXPLAIN ANALYZE is available only when the backend classifies the statement as safe: MySQL, a single read-only SELECT/CTE, no comments or user variables, and a short statement timeout because analyze executes the query. Implementation workflow agents working on tkadauke/syrus or a registered fork of that repository also receive the read-only read_performance_diagnostics MCP tool through that plugin. Scheduled prompts that target Syrus performance work can ask the agent to call it before editing code. The tool uses the same current-revision/all-revisions filtering as the admin payload, returns bounded grouped summaries, and only includes sanitized raw recent events when explicitly requested.
Direct Jobs
Direct Jobs are for work that should start from an operator prompt instead of a GitHub issue. Choose a repository, title, priority, optional provider, prompt, and attachments. Syrus creates a direct Job and runs the normal Initial workflow. API clients and chat proposal cards can also supply a planned execution override. Without an override, Syrus infers primary implementation capabilities conservatively before the Workflow starts: backend work stays on default/Linux compute, iOS/Xcode work uses macOS, mixed iOS plus backend work uses macOS as the primary placement, and ambiguous cross-host requests ask for a split or explicit host instead of guessing.
Chat proposal cards for a single direct Job can be routed to Backlog before confirmation. The default remains Start normally. A Backlog-routed direct Job is created as owned planned work, keeps its chat link, dependencies, and media attachments, and does not create an initial Workflow or Run until it is released from backlog.
When the creator is a product_owner, Syrus holds the Job in needs_triage instead of starting implementation. A developer or admin can open the repository overview, review Jobs waiting under Needs triage, and release each one into the normal triage flow.
Use direct Jobs for internal chores, private context, or experiments that do not need a GitHub issue first. Use GitHub issues when the work should be visible in the repository's ordinary planning flow.
Repositories that configure a branching model in .syrus.yml's delivery: block (a development/release split, a hotfix track, and so on) can select a delivery track per Job instead of always landing on the repository's default branch. The direct job API and admin job creation both accept an optional track selection (e.g. hotfix, when the repository defines a hotfix track); leaving it unset lands the Job on the repository's normal default track. A GitHub issue can select a track the same way with a syrus-track-<name> label (e.g. syrus-track-hotfix). See config/syrus_docs/delivery_tracks.md for the full .syrus.yml shape.
Once a repository configures delivery tracks, its overview page gains a Delivery section: a table of tracks with branch, grade phases, health, landing-queue length, and the last promotion/hotfix-sync into that track; the repository's configured ref-movement actions and whether each is currently available to dispatch; recent promotion/hotfix-sync/upstream-export Workflows with their source and target refs; and recent PR ingestions with their Syrus-provenance classification. The Dashboard's Jobs table shows a small delivery-status badge (waiting for upstream approval, waiting for promotion, syncing hotfix, and so on) next to any Job whose delivery status is more specific than the default "waiting for local approval"/"approved for local landing", and the Jobs sidebar gains matching Waiting for upstream, Promotion pending, and Delivery needs attention smart folders.
A Job's own detail page gains a matching Delivery panel: its selected delivery track and target ref, the pull requests it has opened so far grouped by role (local landing, upstream export, promotion, hotfix sync, external ingest), and a fuller version of the delivery-status copy — including the actual PR number once one is open, e.g. "Sent upstream: PR #123 waiting for review". A fork Job whose repository has configured the send_job_upstream ref-movement action gets a matching Send upstream button in the Job header, so a contributor doesn't have to wait for the automatic post-approval export trigger.
Skills
Skills are named, freeform instruction sets for work that resists being broken into the deterministic Workflow/Step pipeline — broad investigations, judgment-heavy debugging, or one-off operational actions. A skill comes either built in to Syrus or as a repository-local .syrus/skills/<name>/SKILL.md file; a repository-local skill overrides a built-in one of the same name, and Syrus always shows which one actually ran.
Repository-local skill files are limited to 256 KiB. YAML aliases and anchors are not supported in their frontmatter; quote dates, timestamps, and symbol-like values when you intend them to be strings. Invalid frontmatter is shown as a chat notice instead of aborting the turn.
From a repository's overview page, More → Launch skill opens a picker listing every skill available to that repository, along with a form generated from the chosen skill's declared parameters. Submitting creates a Job that runs the skill against the repository, the same way a direct Job runs a free-form prompt. The picker flags a repository-local skill that overrides a built-in one, and the resulting Job/Run detail view records the same provenance so it stays visible after the fact. A scheduled task (see Schedules below) can fire a skill on a recurring or one-shot cadence using the same picker and parameter form, instead of a free-form prompt. A repository-scoped chat (see Chats above) can also run a skill directly with a /skill-name key=value ... slash command, instead of launching a Job.
Account Settings
Each user owns their own profile, credentials, defaults, and preferences:
- Profile stores display name, role, GitHub handle, avatar, bio, and public team profile fields.
- Credentials stores GitHub PAT fallback, Claude credentials, Codex credentials, and the admin API token panel for admins.
- Credential Store is the management surface for generic scoped credentials
owned by the bundled credential_store plugin. It stores payload material as encrypted write-only blobs, exposes only safe metadata and last-used audit details, and lets operators scope credentials to a user, repository, team, or the whole instance. Its brokered tools include deferred MCP SSH command execution, a runtime CLI syrus credential ssh-agent wrapper for SSH deploy scripts, and syrus credential exec for non-SSH script credentials. Wrapper intent can be declared in .syrus.yml scripts: metadata using credential handles, never payload material. The broker enforces scope, surface, tool, type, target constraints, expiry, and revocation. Omitted target constraints are unrestricted, while present empty constraints deny access; URL prefixes require the same HTTP(S) origin and a traversal-safe path-boundary match. Wrappers use temporary process-local material, redaction, cleanup, and access audit events. The MCP SSH tool accepts a restricted ASCII account name plus an ASCII hostname or raw IP address (including an unbracketed IPv6 address); it rejects option-like targets, whitespace, and control characters before invoking OpenSSH. SSH execution runs only read-only diagnostic commands by default; other commands and unsafe shell syntax require explicit workflow authorization that chat callers cannot grant themselves. The audit trail includes denied SSH attempts, bounded command evidence and completion status, credential create/update policy changes, and explicit un-revocation during rotation. Runtime wrapper completion events must match an unexpired lease issued for the same credential, tool, and runtime context, and each lease accepts only one completion event. Name-based leases ignore inactive credentials, prefer user scope over repository, team, and instance scopes, and reject equally ranked ambiguous matches. Personal user-scoped credentials remain private to their owner even from other administrators' agent runs.
- Agent Settings and Credentials store the default agent provider,
max-turn setting, auto-approval fallback, credentials, and "My default routing" rules. Repository settings can layer repository-specific routing on top, and Job detail can add job-specific routing overrides.
- Preferences stores account-level toggles such as scheduling pause and the optional mobile chat focus mode that auto-hides the top app header while scrolling down in a chat.
- Connected Platforms links the Syrus account to external messaging platforms (see below).
- Theme — light, dark, or system (matches the OS color-scheme preference and updates live if it changes) is picked from the account area. The choice applies before the page finishes loading, so there's no flash of the wrong theme, and it's respected by the desktop tray app too.
- Color theme — a theme (19 built-in options — Terracotta, Ocean, Forest, Sunset, Lavender, Slate, Rose, Amber, Midnight, Mint, Plum, Sand, Sky, Crimson, Moss, Coral, Steel, Violet, and Console — plus any custom themes the user owns) is picked from the same account area, independently of light/dark mode. Every theme sets its own color palette; Console additionally proves the theme model goes beyond color, swapping in sharp corners, no panel shadow, a tighter control/spacing rhythm, and an all-monospace type stack. Each built-in theme works in both light and dark mode. The choice applies before the page finishes loading, the same as light/dark mode, so there's no flash of the wrong theme either.
- Language — operators can select their preferred display language from the profile settings page. Supported locales are English (
en), German (de), and Latin (la). The preference is stored per-user and applied to all app chrome and shared UI text. - Design System — when the bundled
syrus_devplugin is enabled, Admin → Design System (/admin/design_system) renders Syrus's real shared UI components alongside the current token values — both the color tokens and the non-color shape/shadow/spacing/density/typography token groups — including the DOC-27 semantic page, section, notice, text, surface, toolbar, chip, and layout helpers. The gallery is development/internal tooling, not a normal account setting. The hidden/design_systempath remains as the render target the chatpreview_themetool opens in a popup; appending?theme_id=<id>previews that theme's full token set (color and non-color) against the same real component gallery, scoped to the page's own content only — it never changes the theme of the surrounding app chrome or any other open tab.
Connected Platforms
Users can link their Syrus account to external messaging platforms such as Telegram via Settings → Connected Platforms. Each linked identity stores the platform name, the platform's stable user identifier, and an optional display handle.
The linking flow uses a short-lived signed token (valid 15 minutes). When the operator clicks Connect for a configured platform, Syrus generates a token and shows instructions — for Telegram, this means messaging the configured bot with /start <token>. Once the platform polling handler verifies and consumes the token, the UI updates automatically via an ActionCable event without requiring a page refresh. Operators can Disconnect any linked account at any time.
Platform buttons show as Not yet available when the instance administrator has not yet configured that platform integration. Administrators set each platform's bot token in the admin settings page (Telegram and Discord each have their own section there). Every configured platform's connector job (PollTelegramUpdatesJob, Discord::GatewayConnectionJob, etc.) can be started from its own section's Start polling button, or it starts automatically on application boot when SYRUS_ROLE is set. Under the hood each button calls the same shared admin API (below), which re-primes every configured connector in one request; each section only reports the outcome for its own platform, so clicking Telegram's button and seeing Discord's Gateway listener also get re-enqueued in the background doesn't show up as a Telegram-flavored message.
Inbound message routing — When a platform polling handler receives a message from an external user, InboundMessageRouter looks up the sender's PlatformIdentity, finds or creates a ChatSession scoped to that platform and user, records the message, and enqueues a ChatTurnJob when the session policy is speak_when_spoken_to.
Outbound delivery — When Syrus replies (an assistant-role ChatMessage is created in a platform-origin session), a PlatformDelivery::Registry adapter delivers the message to each participant who has a linked identity for that platform. Participants with no linked identity receive the reply via ActionCable on the web UI as usual. Platform adapters are registered at load time; the web adapter is a no-op since ActionCable already handles it. Beyond the built-in web and Telegram adapters, a plugin gem can register its own external platform through the :platform_delivery plugin extension point — once registered and enabled, its platform automatically appears as a Connected Platforms option, with no core code change required. Discord is the first such plugin (plugins/discord/, disabled by default): instead of long-polling, it holds a persistent, bot-initiated outbound WebSocket connection to Discord's Gateway, so linking (a DM /link <token> command) and message delivery work the same no-inbound-webhook way as Telegram once an administrator sets a Discord bot token and enables the plugin.
Starting the polling worker — Platform polling workers self-reschedule after each poll cycle. On application boot, registered workers are started automatically when SYRUS_ROLE is set — core connectors and plugin-provided connectors both start automatically, but a plugin's connector only starts while its plugin is enabled. Administrators can also trigger a manual start of core _and_ plugin connectors (e.g. Discord's Gateway listener) via the admin API:
POST /api/v1/app/admin/platform_polling/start
This enqueues any registered platform polling job, core or plugin, that is not already running, and reports each connector's status (started, already_running, or not_configured) in the response so an administrator can see whether a specific connector actually restarted.
The self-reschedule that keeps a connector alive only runs if its worker process reaches the end of its poll cycle. A worker that gets killed or pruned mid-cycle (an OOMKill, a deploy, an infrastructure hiccup) can drop a configured connector out of the queue with no visible error — it looks identical to "nothing has happened yet." A background watchdog job checks every few minutes for exactly this and automatically re-enqueues any configured connector that has gone missing, logging a warning so the gap is visible in the logs even if no administrator happens to hit the manual restart button. Every listener discussed here — Telegram's long-poller and Discord's Gateway connection alike — runs in-process inside a Solid Queue worker pod; there is no separate bot daemon to restart.
Credentials are stored with Active Record Encryption in the Syrus database, so every web, worker, console, and migration context that reads users needs the same stable ACTIVE_RECORD_ENCRYPTION_* keys, or RAILS_MASTER_KEY when those keys live in Rails credentials.
Input Sources
Repository settings list installed input source plugins from the Syrus plugin registry. Bundled installs include GitHub and Linear sources; each source provides its own settings schema so the repository form can render fields without hardcoded per-source templates. GitHub keeps the standard trigger-label flow, while Linear can poll a team and optional label filter.
Plugin Visibility
Admins can open Admin → Plugins to inspect the plugin registry without checking the Gemfile. Each plugin shows its own icon — a real brand mark for plugins like Ruby, Rails, JavaScript, Java, Kotlin, Android, Python, Django, Go, GitHub, Discord, Linear, and Claude, and a shared SPQR-eagle placeholder for plugins with no natural mark of their own — the same icons also appear in agent/chat provider pickers and next to a Workflow's detected-language list. The page lists each registered plugin's version, enabled state, default enabled policy, disableability, category, extension point classes, and basic author/source metadata when available. Disableable installed plugins can be toggled live for new requests and sidecars. Installing or removing plugins still requires changing the Gemfile and restarting Syrus. Disabled bundled plugins with their own operator docs, such as Review Notes, withhold their routes and extension-point providers while retaining previously written plugin records for when they are re-enabled. The list uses the same chip-based filter bar as other admin list pages: a Category chip filters by the plugin category taxonomy (language, agent provider, input source, MCP tool set, and the rest), and a Search chip filters by name, description, and category — a simple full text search, not a full search-engine integration. Chips combine together, and there's no saved-filter sidebar here, just the filter bar itself.
Plugins can declare dependencies on other plugins (for example, rails depends on ruby). The page shows both directions of that relationship — what a plugin depends on, and what depends on it. Enabling a plugin silently enables its dependencies too. Disabling a plugin that other enabled plugins still depend on asks for confirmation first, listing exactly which plugins would also be disabled, before cascading the disable through all of them.
Tailscale
The bundled tailscale plugin exposes a Syrus installation on the operator's Tailscale network, so it can be reached from laptops and phones away from the local network. It ships installed but disabled by default; enabling it from Admin → Plugins and setting a TS_AUTHKEY auth key runs tailscaled in its own dedicated, privileged container -- started by Plugin Runtime, not the worker -- and forwards Tailscale traffic to the Rails app. The worker itself never needs any elevated network capability.
Once enabled, Admin → Tailscale shows whether the daemon is running and connected, a copyable https://<device>.ts.net URL, and a short setup checklist. To reach Syrus from a phone: install the Tailscale app, sign in to the same tailnet, then open that ts.net URL.
Lifecycle actions (stop, start, restart) and container health live on the shared Admin → Plugin Services page rather than the Tailscale page itself. See Tailscale isn't connecting for a step-by-step guide covering a missing/unreachable runtime, an unhealthy container, and a daemon that's up but not yet connected.
K8s Cluster Viewer
The bundled k8s_cluster plugin lets admins register external Kubernetes/k3s clusters and browse them read-only from Admin → K8s Clusters. Like Tailscale and MySQL DB Browser, it ships installed but disabled by default from Admin → Plugins.
Register a cluster by pasting its kubeconfig — Syrus resolves the current-context's cluster and user, then stores only the API server URL and non-secret connection metadata on the cluster record. The kubeconfig is encrypted at rest in Credential Store. Admins choose whether its credential is usable by one user, a repository, a team, or the whole instance; chat and workflow agents only see the cluster when their user and run context can use that credential. Existing instance-scoped clusters keep their visibility, while legacy inline credentials remain admin-only until migrated. A Test button checks connectivity before saving, and an explicit insecure_skip_tls_verify opt-in skips certificate verification for a self-signed k3s API server.
Each registered cluster has a Browse action that opens a tabbed, read-only viewer: Overview (node count/status plus aggregate CPU/memory from metrics-server, when installed), Workloads (pods, Deployments, and CronJobs, switchable per kind), Services (services alongside their backing Endpoints' ready/not-ready address counts), Storage (PersistentVolumeClaims), Nodes (capacity, allocatable capacity, and readiness), Events (most-recent-first), Logs (a pod log tail with a container picker for multi-container pods), and Live (a 10-second polling view over pod status and recent events). A namespace filter scopes the namespace-aware tabs to one namespace or all of them.
This is a direct-connectivity viewer — no relay agent for clusters Syrus can't reach directly. Each cluster also has two independent, per-cluster opt-ins for the Syrus agent, both off by default: agentic_access_enabled exposes read-only MCP tools (list/describe namespaces, pods, deployments, services, events, PVCs, nodes, CronJobs, pod logs, and the metrics overview) so agents can inspect a cluster the same way the Browse viewer does. allow_writes — a stricter, separate opt-in that requires agentic_access_enabled too — additionally exposes a short, fixed allowlist of four mutating tools: restart a deployment's rollout, scale a deployment, delete a pod to force a reschedule, and cordon/uncordon a node. There's deliberately nothing broader than that — no namespace deletion, no CRD/RBAC changes, no arbitrary manifest apply, no exec-into-pod — and every write call is audit-logged with a before/after summary of what changed.
For ad hoc read-only inspection, the plugin also provides a credential-backed k8s_cluster_kubectl tool. It leases a scoped k8s_cluster.kubeconfig credential and accepts only get, describe, logs, top, events, api-resources, api-versions, version, explain, wait, and diff, plus auth can-i and auth whoami. Mutating commands and arbitrary kubectl plugins are rejected even if the deprecated risky-command input is supplied. Kubectl runs with only KUBECONFIG, PATH, and HOME in its environment, under a timeout with stdout and stderr bounded to 64 KiB each. Pod-log reads cap the requested tail at 1,000 lines and 64 KiB. When a credential belongs to a registered cluster, that cluster's agentic-access opt-in applies to every command. Workflow and chat agents cannot grant themselves the Secret-output override; it requires an operator-controlled caller.
GitHub App And PAT Behavior
For most API operations (reading issues, checking CI status, listing PRs, etc.), repositories prefer an active GitHub App installation when one is linked for the repository owner. If no active installation is available, Syrus falls back to the user's PAT.
When an App-authenticated API call later hits an authorization boundary such as a GitHub 404, Syrus retries with a refreshed App installation token once, then falls back to the user's PAT if one is available. That fallback is recorded as a per-repository diagnostic and, when it happens during a Workflow run, the Job's credential_mode is updated to pat so the visible telemetry reflects the credential that actually worked.
Commit and pull request authorship is different: for an ordinary Job that belongs to a real person, the Job owner's own connected GitHub PAT wins over the shared App installation whenever one is connected, so commits and PRs are attributed to the actual human rather than the shared bot. The App identity is used only when the owner has no PAT connected, or for infrastructure/system-initiated Jobs (main-branch grading and repair, deploys, and similar) that have no real human requester — the _git commit_ for those always carries the bot's name and email, regardless of what PAT the nominal Job owner happens to have connected, since that's just local metadata and needs no live credential. Opening the PR still needs a real GitHub credential, though: if this repository's App installation happens to be inactive when an infrastructure Job runs, Syrus falls back to whatever PAT is available rather than failing outright, and credential_mode records pat for that run so the mismatch between the bot-authored commit and the PAT-opened PR is visible to operators. A commit authored by the shared bot on behalf of a human gets a Co-Authored-By: trailer naming that human; a commit authored directly as the human owner does not need one. Jobs persist the selected credential_mode as app or pat so operators can tell which identity actually authored that run's PR.
For PAT setup, create a classic token with repo for private-repository use, or public_repo instead when you intentionally need public repositories only, then choose an expiration you can rotate. The workflow scope is optional; add it only if agents must modify GitHub Actions workflow files through the PAT. Classic tokens do not need a repository probe. Fine-grained tokens remain supported through the secondary owner/repo validation path in Credentials.
The admin Installations page includes a lightweight GitHub App diagnostic. It shows recent installation sync status, repository-to- installation link state, removed installation rows, missing GitHub IDs, and the concrete reason a repository is falling back to PAT. Admin chat agents can read the same diagnostic through admin_github_app_installation_diagnostic.
Clone remotes use anonymous GitHub URLs. Token-bearing push URLs are constructed for the individual push command and are not written into .git/config.
Multi-User Model
Syrus works well as a single-user deployment for one operator's own repositories, and it can grow into one deployment serving multiple users. Users have their own role, repositories, credentials, provider defaults, schedules, chats, Epics, Jobs, and admin/API permissions. Repository settings can override the user's provider default when a codebase needs a specific agent.
When more than one user exists and the default-disabled Team Directory plugin has been enabled, the app shows a team directory with each member's role, profile link, and lightweight workload counts. Single-user instances keep the primary navigation focused on the solo operator's work.
That model keeps solo operation simple and team operation centralized without turning every run into a shared global credential.
Repository Members And Access Tiers
Each repository has its own members, independent of the global user role. Access is granted through one of three tiers: Read (view Jobs and Epics on the repository), Write (also approve, retry, and cancel Jobs), and Admin (also manage repository settings, credentials, and members — the tier the repository's original owner always holds).
A repository's edit page (Repository → Edit) lists current members and their tier, and lets an Admin-tier member add someone by email at a chosen tier, change an existing member's tier, or remove them. There is always at least one Admin-tier member on a repository; Syrus refuses to remove or demote the last one.
Teams
Teams (Admin → Teams) are a bulk-grant mechanism layered on top of direct repository membership, not a replacement for it. A team has a name and a roster of members, each either an Owner (can rename the team and manage its membership) or a plain Member. Any signed-in user can create a team and becomes its first owner.
A repository's Admin-tier members grant a team access from that same edit page, at a chosen tier (Read/Write/Admin) — exactly the same three tiers direct membership uses. Every member of a granted team then gets at least that tier on the repository, alongside whatever direct membership they hold. A team with no repository grants, or a repository with no team grants, behaves exactly as it did before teams existed — teams are purely additive. Removing a repository's last team grant never removes its direct memberships, and vice versa.
For team workflows, fork-based development, and open source contributions, see Collaboration.
GitHub Permission Parity
GitHub is still the actual source of truth for who can push code today, so a background sync periodically compares Syrus's Write/Admin-tier repository members against real GitHub collaborator permissions on repositories with a GitHub App installation, and flags drift in either direction. This is detection and surfacing only — Syrus never grants, revokes, or otherwise changes GitHub access as a result of this check.
A Write/Admin-tier member whose GitHub collaborator permission doesn't back up their Syrus access — no linked GitHub handle, not a collaborator on the repository, or a GitHub permission below their Syrus tier — gets a "GitHub mismatch" badge on their row on the repository edit page. This is the more important direction to know about: Syrus would otherwise let that person approve, retry, or cancel Jobs without matching commit rights.
A GitHub collaborator with write or admin access and no corresponding Syrus access at all shows up in a separate "GitHub-only collaborators" list on the same page. Both signals refresh automatically; there's nothing to configure. This groundwork matters more once self-hosted git removes GitHub as a required central host for a repository.
Spending Insights
Spending Insights is installed but disabled by default on fresh installations; an admin can opt in from Admin → Plugins.
The spending dashboard at /insights/spending rolls up runs.cost_usd into operator-facing cost views. Syrus records provider-reported cost when available; runs whose providers omit dollar costs keep cost_usd unset. The cost is assigned to the Run's completion date; unfinished Runs are omitted even if they already carry a partial cost. Charged chat turns are recorded separately when they complete, so new spend in an older chat remains visible in the correct reporting window. Chat cost recorded before this ledger existed is not assigned to a guessed historical date. The dashboard shows week, month, lifetime, average Job, and average merged-PR totals, plus breakdowns by Epic, user, repository, trigger kind, a daily trend chart, and the most expensive individual Runs. When spending exists across multiple agent providers, the dashboard can filter those views by model provider such as Claude Code or Codex.
The date window controls the trend, breakdowns, and top Runs, while the week, month, and lifetime summaries keep their calendar meanings independently of that window. Lifetime combines workflow and chat costs. CLI date bounds can also be supplied independently to select everything since or until a date.
Chat can query the same spending data for 7-, 30-, or 90-day windows, optionally narrowed to a repository or Epic, and returns the daily trend, token totals, and the most expensive Runs for the current operator.
The view respects the same ownership model as the rest of the app: non-admin users see only their own Run and chat costs, while admins see global totals across the instance.
Operator Briefing
Operator Briefing is a plugin, disabled by default; enable it from Admin → Plugins. The /briefing page gives each operator a per-repository briefing surface with one tab for each enabled repository subscription. Opening the page can create disabled subscription rows for visible repositories so the settings modal has toggles to show, but scheduled generation remains opt-in.
Briefings are generated by repository-scoped infrastructure Jobs. Scheduled generation only considers operators with enabled repository subscriptions, and only starts a new briefing when repository activity changed since the previous closed briefing; manual regeneration always runs without enabling future scheduled generation by itself. Starting a new briefing supersedes the previous live briefing for that repository and keeps older revisions browsable as read-only history.
Briefing revisions render typed content blocks: narrative, small fixed-shape charts, image and artifact references to existing typed artifacts, and link cards that point back to the Jobs and Workflows behind the summary. Artifact blocks are read-only references to artifacts already captured by the summarized Workflow.
The workflow-list evidence tool uses the same bounded MCP pagination contract: one-based page numbers are capped at 10,000 before database offsets are built, while ordinary out-of-range pages return an empty list.
Narrative spans can start wiki-style dives. The briefing can seed a small set of "worth a closer look" spans, and operators can also select arbitrary text and click "More info." Both paths start a briefing_dive follow-up Workflow on the same live Briefing Job and produce or revise a durable repository-scoped topic page.
Mockups
In a planning chat, an agent can sketch an interface — HTML, CSS and JavaScript — in a scratch area that never touches your repository, and show it to you in the chat sidebar.
Published sketches become mockups: each gets a stable MOCKUP-123 reference, and a Mockups entry in the sidebar lists them with a searchable filter bar. Selecting one opens it beside the list for a quick look, so a filtered list stays put while you browse.
Republishing a mockup updates it in place, so the reference keeps working while an agent iterates. Closing the preview tab only dismisses it from chat; the published mockup remains viewable from the Mockups page. Mockups are visible to whoever can see the chat they came from.
Ships as a bundled plugin, enabled by default. Turning it off from Admin -> Plugins removes the page and the chat tools; mockups already published are kept.
Agent Insights
Agent Insights is a plugin, disabled by default; enable it from Admin → Plugins. Agent insight runs inspect recent repository automation history and create structured suggestions the operator can accept, dismiss, or promote into follow-up work. Repository pages show per-repository suggestions, while admins can use the admin insights view to inspect suggestions across the instance. Insight runs also review pending/accepted/dismissed insights and repository memories for freshness. If a memory is stale or describes a fixed bug, the agent proposes a remove-memory insight; accepting it soft-deletes the target memory through the normal audited memory path instead of letting the analysis agent delete memory directly. Stale, duplicated, or superseded pending or dismissed insights are retired (an audited state change, not a delete) instead of piling up meta-insights that only say another insight is stale; retired insights drop out of the default active review but stay inspectable through a "Retired" filter alongside Pending/Accepted/Dismissed/All.
Repository collaborators, including users with access through a team, can view and discuss suggestions and inspect insight scheduling. Suggestion mutations require repository write access, while changing the repository-wide schedule requires repository admin access.
While the plugin is enabled, regular chat agents can discover and call list_insights, read_insight, accept_insight, dismiss_insight, and retire_insight for suggestions in the chat's attached repositories. Admin chat agents can inspect all repositories and can narrow broad reads with repository, state, page, and limit filters. Each mutation accepts one insight id or a bounded, deduplicated array; multi-insight changes are validated together and require one grouped operator confirmation before any record changes. retire_insight is an audited user action for clearing stale, duplicated, or superseded suggestions, can cite a superseding Job, and keeps the accepted-insight guard: accepted rows require an explicit retire_accepted: true. Suggestion creation and revision remain limited to insight workflow agents through submit_insight and update_insight. Agent Insights list and transcript tools cap one-based pages at 10,000 before database offsets are calculated; ordinary out-of-range pages remain empty.
Repository Throughput Metrics
Repository throughput metrics use a versioned contract derived from existing Syrus records. Each repository window reports counts, per-hour rates, sample counts, and confidence labels for PR creation, committed output, landing throughput, landing waste, and the review funnel. Landing throughput treats a single auto-merge as one landing unit with one Job and an Epic merge train as one landing unit with its full member count, so operators can compare landing units/hour with Jobs landed/hour.
The canonical windows are 1h, 4h, 24h, and 7d, plus a one-hour last-active fallback when recent activity is sparse. Low-sample windows are labelled instead of treated as stable averages, so operators can distinguish "nothing happened recently" from "the repository is slow."
The Throughput plugin precomputes every active repository's snapshot every five minutes on the polling queue, including repositories whose issue polling is disabled. A snapshot is considered fresh for ten minutes and retained for up to 24 hours, so a failed refresh can serve clearly marked stale data instead of plausible empty metrics. A cold request may perform one computation; if another process already owns it, the API returns an explicit temporary unavailable response. Snapshot keys include the repository and contract version, and refreshes are serialized per repository with a five-minute lease around a four-minute computation bound.
Landing windows also separate successful, failed, cancelled, and deferred attempts; report grader-phase time, mergeability/rebase wait, base-moved regrades, cached validation reuse, failed train cooldown waste, and a current optimistic capacity estimate from recent successful landing-unit wall time.
Review funnel windows count Jobs that received PR feedback before approval, Jobs approved without feedback, feedback rounds per Job, and approval sources where Syrus can distinguish operator, bulk, auto-rule, and GitHub-review approval. They also report sample-sized latency distributions for PR open to first feedback, feedback to addressed, PR open to approval, approval to landing start, and approval to landed.
Repository Automation
Repository settings control trigger label, polling, default branch, provider override, prepare behavior, PR cost footer, auto-merge settings, and approval behavior. The repository issues panel can list GitHub issues and delegate work by adding the trigger label through the same credential path Syrus uses for polling.
GitHub issue ingestion is polling-based and runs about every five minutes by default. The issue body can declare Epics, attach child Jobs, and express dependencies with epic:, depends on:, and blocked by: markers; see Issue Authoring for the full contract and examples.
Polling also tracks how many open GitHub issues on each repository are not carrying the trigger label — issues Syrus never ingests because no one labeled them. The Dashboard's Jobs view surfaces this as a dismissible cross-repo notice ("N unlabeled open issues across M repositories") with links straight into each repository's GitHub Issues tab, so operators notice unlabeled bug reports without visiting every repository one at a time. The notice only appears on the Inbox smart folder, directly above the data table, since that is where new unlabeled work would otherwise go unnoticed.
Repository polling is quota-aware. If GitHub reports an exhausted API bucket, autonomous polls wait until the reset time instead of advancing the repository poll watermark and failing. The unlabeled-issue count is cached rather than fully paginated on every tick, and linked-PR discovery is batched with per-issue watermarks so unchanged issues do not keep spending GraphQL calls. Incremental issue polls are backed by a scheduled full reconcile: by default, once a repository has gone 24 hours without a successful full issue poll, the next autonomous poll reads open trigger-labeled issues without GitHub's updated_at cursor. This bounds the case where a label was applied during a polling gap but the issue itself was not edited afterward. Admins can tune the interval with repository_full_poll_interval_hours; the full reconcile keeps the same linked-PR lookup budget as normal polls, so large backlogs are worked through over multiple ticks.
When approval propagation is enabled, Syrus mirrors eligible Job approvals as GitHub PR reviews. It posts as the approving user's own connected GitHub account whenever their GitHub identity differs from the PR's author; when the approver _is_ the PR's own author (a true GitHub self-review, which GitHub always rejects from the same identity), Syrus posts the review as the GitHub App instead, crediting the real approving human in the review body.
If a labeled GitHub issue was created by a Syrus user whose GitHub handle maps to a product_owner account, polling creates the Job in needs_triage. Developers and admins release those held Jobs from the repository overview before classifier triage or implementation can start.
Triage itself is automatic: Syrus reads each new issue and decides whether it belongs to an Epic, duplicates existing work, or is ordinary new work. When it cannot tell — a vague report, or a request that could mean several things — it says so instead of guessing, and the Job page shows Accept and Reject alongside the reason it was unsure. Accept queues the work; Reject closes the Job. The Jobs dashboard can also accept multiple classifier-uncertain Jobs at once from the bulk toolbar. Syrus retries the classification once on its own first, so a passing provider error does not put an issue in front of you unnecessarily.
When a repository is registered as a fork of an upstream that also lives in the instance, Jobs on the fork branch off — and open their pull request against — the upstream's default branch (head = the fork's branch, base = the upstream's main). A fork can also keep its own default branch in sync with the upstream on a schedule: toggle Auto-sync this fork's default branch from its upstream in the repository settings, or click Sync now at any time. Auto-sync keeps main-branch health detection on the fork from going stale and is independent of the per-Job base branch.
For command examples, continue to Recipes. For failure paths, use Troubleshooting.