Zen Mode
View as markdownBy default the orchestrator is the scheduler and you are the priority-setter + reviewer. Zen Mode flips it: the orchestrator takes the lead. It triages mechanically-eligible backlog into todo on its own and lands finished work past a pre-defined confidence bar.
The mental model is lead vs. scheduler. Zen on, the agent makes calls; you redirect when you disagree. Zen off (the default), the agent waits to be told. Same prompt, same workspaces, same board. The only thing that changes is who initiates.
Turning it on
Ways to flip the switch:
- CLI:
shelbi zen on,shelbi zen off,shelbi zen pause. - Palette: Turn Zen Mode on in the command palette
(
Ctrl+P⌘K).
- Hotkey:
Alt+Z(theglobal.zen_togglebinding) toggles between On and Off while the Activity view has focus.
- Sidebar: click the Zen pill at the bottom of the sidebar.
- Shortcut / menu:
⌥Z, or View ▸ Turn Zen Mode On.
- Status check:
shelbi zen statusprints the current mode, configured local checks, resolved danger paths, the last orchestrator crash (if any), and the count of in-flight Zen issues.
The first time you turn Zen on from the TUI or the desktop app, Shelbi shows an Enable Zen Mode? dialog explaining what Zen does (Cancel / Enable Zen, with a "Don't show this again" box).
When Zen is on, the sidebar shows a green ZEN MODE ON band on its bottom row, below the toolbar. That band is the at-a-glance "the agent is in lead mode" signal. If it's not there, Zen is off or paused.
When Zen is on, the pill at the bottom of the sidebar turns green and reads Zen mode on. That pill is the at-a-glance "the agent is in lead mode" signal. Off or paused, it reads Zen mode off.
Rebinding the hotkey
The toggle is the ordinary keybinding global.zen_toggle, bound to
Alt+Z (⌥Z) by default. Some terminals (notably macOS Terminal.app, where
Option/Alt types accented characters) swallow Alt+Z. If yours does,
rebind it in Settings ▸ Keybindings, or set
defaults.global.zen_toggle in ~/.shelbi/keys.yaml:
defaults:
global:
zen_toggle: ctrl-gYou can always use shelbi zen on|off or the palette instead.
Turning it off
Three levels of "stop":
shelbi zen off(or the Zen toggle in the app): hard off. No new auto-promotions, and the orchestrator stops initiating the merge bar on anymerge-action transition. Anything already mid-flight in the conditions flow runs to completion on its current trajectory.shelbi zen pause: softer. No new auto-promotions, but the merge bar keeps applying to in-flight issues that reach amerge-action transition. Use this when you want to triage incoming work yourself without losing validation already in motion.- Per-issue pull-back:
shelbi issue move <id> --to backlogfrom a handoff status pulls a specific card out of Zen's hands without changing the global mode.
Crash recovery
A crash does not turn Zen off
Zen Mode is not currently auto-disabled when the orchestrator crashes.
The daemon relaunches a crashed orchestrator session and Zen stays in
its current mode. Run shelbi zen off if you want it off while you
review in-flight work.
The orchestrator's bootstrap still scans recent event lines for a
zen=off reason=crash-recovery line and calls it out in its first reply,
but current versions of Shelbi no longer write that line; you'll only see
it in logs from older releases.
The zenmode.md file
This is the power-user invitation. Both halves of Zen policy, the auto-promote judgment categories and the merge conditions, live in one user-owned file per project:
~/.shelbi/projects/<name>/zenmode.md(For an in-repo project it resolves to <repo>/.shelbi/zenmode.md
instead.)
zenmode.md is the live source of truth for what Zen Mode means in
that project. The orchestrator reads it in full at bootstrap and
re-reads it periodically, prompted by the heartbeat, to keep its
behavior aligned with your intent. The Rust side never inspects the
prose. The file exists purely for the orchestrator, so you can edit it
freely to tune Zen, and your changes take effect without a rebuild.
shelbi reload preserves your edits to
zenmode.md, leaving your prose intact.
The first line is the heartbeat summary
The first line of zenmode.md is the one-line summary the heartbeat
echoes back to the orchestrator. While Zen is on, every few ticks a
zen=on heartbeat carries that
first line verbatim as a live reminder of what Zen means here, read
fresh each time so an edit shows up immediately. Roughly once an hour
the heartbeat instead injects a fuller "re-read zenmode.md now"
instruction, so the orchestrator refreshes the full policy before its
next Zen decision rather than acting on a faded memory.
Because that first line is what gets re-injected, keep it a single accurate line: if you change what Zen means below, change the first line too. It is the fastest lever on the orchestrator's live behavior.
The file shape
A zenmode.md is the summary line, then the policy sections the
orchestrator applies. This is the exact file shelbi writes for a new
project:
Zen: auto-promote eligible backlog, review each handoff yourself (diff vs the issue's acceptance criteria + scope), and finalize passing work by moving it to done (the workflow's review->done transition owns the merge) without asking.
# Zen Mode policy
This file is the source of truth for what Zen Mode means in **this** project.
The orchestrator reads it (and re-reads it periodically, prompted by the
heartbeat) to keep its behavior aligned with your intent. The Rust side never
inspects the prose below, so edit it freely to tune Zen for your project.
The **first line of this file is the one-line summary** the heartbeat echoes
back to the orchestrator every few ticks. Keep it a single line and keep it
accurate: if you change what Zen means here, change that line too, because that
is what gets re-injected as a live reminder.
When Zen is on the orchestrator acts as lead rather than scheduler. It may:
1. Auto-merge work that passes the merge-conditions flow below.
2. Auto-promote `backlog`-category issues into `ready` when they fit at least
one of the judgment categories below.
When Zen is `paused` it stops starting new auto-promotions but keeps applying
the merge-conditions flow to in-flight issues that reach `handoff`. When Zen is
`off` the orchestrator is a plain scheduler: it dispatches what you promote and
leaves handoffs for you.
## Auto-promote judgment categories
The mechanical eligibility scan (`shelbi zen scan`) hands the orchestrator
`backlog`-category issue ids that are safe from a state-machine standpoint: not
blocked, not opt-out, no file overlap with anything in flight. On an external
issue tracker (e.g. `github`) the scan **also already excludes any issue not
assigned to you** (the account Shelbi is authenticated as), so an outside
contributor's bug report or someone else's issue never reaches this list, and if
that login can't be resolved the scan returns nothing rather than guessing. You
don't need to re-check ownership; trust the list. From it, **only auto-promote**
a candidate if **at least one** of these is true:
1. **It's the kind of work the user generally trusts you with.** Look at the
`done`-category column. Does the user routinely accept issues of this shape
without changes? (Examples: docs typo fixes, dependency bumps, content
sweeps that match a recently-stated convention.) If their done-history shows
pattern acceptance, this is in scope.
2. **It's part of fixing an issue the user recently raised.** Did the user
mention this bug, feature, or concern in conversation in the last few turns?
Issues that respond to something the user explicitly asked for are in scope.
3. **It's part of a larger body of work the user explicitly kicked off.** If
the user filed a batch of related issues (e.g. 13 vs-pages, a multi-step
refactor), the remaining items in that batch are in scope.
If a candidate fits none of these, **leave it in `backlog`** and surface it in
your next user-facing reply: *"I considered promoting `<issue>` but wasn't sure
if it fits your intent, want me to?"* Also emit
`reason=orchestrator:zen-decline reason-text=<short>` on the issue so the
activity feed shows what you considered.
For ones you do promote, run `shelbi issue move <id> --to <ready-status>
--reason "orchestrator:zen-promote category=<n>"` (`<ready-status>` is the
literal `ready`-category status name from the active workflow, `todo` in the
default) and then dispatch per the normal auto-dispatch contract.
## Merge conditions
**First, the review-workspace gate.** If the project declares review
workspaces, an issue reaching `handoff` is destined for a **human** to inspect on
a running server, that's the entire point of the review workspace. So do
**not** run the merge-conditions flow on it, and do **not** merge it: leave it
in the review status for the human to load from the sidebar and resolve (there
is no `shelbi review` command for you to run). The human accepts by moving it
to `done` (which is when it merges per existing rules) or bounces it back to
`ready`. Zen never auto-merges a review-routed issue out from under the human.
Only when the project has **no** review workspace does `handoff` mean "ready to
auto-merge", the case the rest of this section covers.
When an issue enters `handoff` and Zen is on (and the project has no review
workspaces), run `shelbi zen probe <task-id>` to get a JSON report covering
local checks, merge conflicts, diff size, and danger-path matches. A passing
mechanical probe is necessary but not sufficient; your own review of the diff
(step 5) is what checks the branch against the issue's intent. Apply these
conditions to the report:
1. **All `local_checks` must have `exit_code == 0`.** If any failed, leave in
`handoff` and emit `reason=zen:failed-checks` with the failing command +
output tail.
2. **`merge_conflict.conflicts` must be `false`.** Otherwise emit
`reason=zen:merge-conflict` with the files.
3. **`diff_size.files` <= 30 AND `diff_size.lines_added + lines_removed` <=
2000.** Otherwise emit `reason=zen:diff-too-large` with the stats.
4. **`danger_paths.matched` must be empty.** Otherwise emit
`reason=zen:danger-path` with the paths.
If all four pre-PR probe conditions pass, review the diff against the issue
yourself before opening a PR; only if your review passes do the CI + merge
steps run:
5. **Review the diff against the issue.** A green mechanical probe proves the
branch builds and does not conflict; it does not prove the branch does what
the issue asked. Read the issue's acceptance criteria and the branch diff
(`git diff <base_branch>...<branch>`) yourself and confirm:
- the diff satisfies every acceptance criterion the issue states;
- the diff stays within the issue's declared scope, with no unrelated files
and, in particular, no large deletions or reverts of code the issue never
mentions (the signature of a branch cut from a stale base);
- the branch carries real new commits ahead of `<base_branch>` and its
content is not already present in the base under a different commit (a
branch whose diff is dominated by undoing recently-merged work, or whose
change is already merged under another SHA, must never be merged).
You are the reviewer here, not the builder: a workspace developer agent
wrote this code, so reviewing it yourself is not grading your own work.
Read the criteria and diff with fresh eyes and don't wave a branch through
just because you dispatched it. If it fails any check, bounce the issue to
`ready` with concrete findings and emit `reason=zen:review-reject` with a
one-line summary. For a project that wants a genuinely independent,
de-biased reviewer instead of this self-review, wire a `qa`/`adversarial`
gate into the workflow (a first-class status) rather than reviewing here.
**The workflow's `review -> done` transition owns the merge — do NOT run `shelbi
zen pr-merge` as part of this finalize.** In the default `task` workflow that
transition is `actions: [push_branch, merge]`, and the earlier `in-progress ->
review` transition already ran `push_branch` + `open_pr`, so a PR for this branch
already exists. Your job is to gate on green CI and then *trigger that
transition* by moving the card to `done`. Running `zen pr-merge` first
double-merges: it squash-merges + deletes the branch itself, and then the
transition's `merge` action finds the branch already merged and strands the card
in `review` ("branch not on origin" / empty-commit failure). One merge, owned by
the workflow.
6. Freeze the probe's `repository`, `repository_id`, `base_branch`, `base_sha`,
`integration_sha`, and `head_sha`. If the branch has no open PR or its
provenance drifted, run `shelbi zen pr-create <task-id> --match-repository <repository> --match-repository-id <repository_id> --match-base-branch <base_branch> --match-base-commit <base_sha> --match-integration-commit <integration_sha> --match-head-commit <head_sha>`
using those exact values, and also pass `--match-published-head-commit <published_head_sha>`
from the probe's `published_head_sha` field, to (re)open and realign the PR so
CI runs. When the probe rebased the branch onto a moved base, `head_sha` is a
local-only commit while `published_head_sha` is the pre-rebase tip the remote
branch still points at; that flag lets pr-create replace the tip it reviewed
instead of refusing it as a concurrent update. Capture the PR number it
prints. Exit code 75 means the push landed but GitHub's PR view had not caught
up yet: re-run the exact same command (it is idempotent). Exit code 1 is a
real mismatch: leave the issue in `handoff` and re-probe.
7. Run `shelbi zen ci-watch <pr-number> --match-repository <repository> --match-repository-id <repository_id> --match-base-branch <base_branch> --match-base-commit <base_sha> --match-integration-commit <integration_sha> --match-head-commit <head_sha> --timeout 15m`
using that same probe identity. Stdout is `green`,
`red:<check>:<summary>`, or `timeout`. The condition: must be `green`.
Any identity movement fails closed; never recompute or substitute a probe
field after CI begins.
8. If green, finalize by moving the issue to `done` — the transition merges:
`shelbi issue move <task-id> --to done --reason "orchestrator:zen-merge pr=<pr-number>"`.
Keep the reason on one line and use the `pr=<pr-number>` kv token exactly —
the event log folds spaces into underscores, and the activity feed reads
`pr=` back out of that normalized form to render the merge badge. The
`review -> done` transition squash-merges into `<base_branch>` and deletes
the branch — this is the only merge. Do **not** run `zen pr-merge` here.
Once the move lands, add the Changelog entry the orchestrator instructions'
`## Changelog` rule describes: `shelbi changelog add --author orchestrator
--title "<what shipped>" --issue <task-id> --pr <pr-number> -d "<what changed
and why it matters>"`.
9. If red or timeout: leave in `handoff`; emit `reason=zen:failed-checks` (red)
or `reason=zen:ci-timeout`.
## Release flow
`shelbi zen pr-merge` is **not** part of the ordinary review->done finalize above
— that merge is owned by the workflow transition. It remains the right tool for
the **Release flow**: version-bump / package-manager PRs (a Cargo version bump, a
Homebrew-tap formula PR) that are *not* governed by a `review -> done`
transition. Once you have authorization to cut a release, carrying it to
published is part of the flow — those mechanical bump PRs get CI-green + scope
verification, not a human review gate. For such a PR, after its CI is green, land
it with the exact-provenance
`shelbi zen pr-merge <pr-number> --match-repository <repository> --match-repository-id <repository_id> --match-base-branch <base_branch> --match-base-commit <base_sha> --match-integration-commit <integration_sha> --match-head-commit <head_sha>`
(or `gh pr merge <n> --squash --delete-branch` for a tap PR on another repo). The
command verifies the candidate has sole parent `base_sha` and the exact tree of
`head_sha`, then atomically advances only `refs/heads/<base_branch>` from
`base_sha` to `integration_sha` with Git's compare-and-swap lease. If branch
protection, rulesets, a merge queue, a fork, permissions, or a non-squash
strategy prevent that exact-ref path, follow the command's human-review action.
## Customization
Edit this file to tune Zen Mode per project. The prose is the source of truth
for both auto-promote and merge policy, and for the one-line summary at the
top.
- **Summary line** — rewrite the first line to match whatever policy you set
below. It is what the heartbeat re-injects, so it is the fastest lever on the
orchestrator's live behavior.
- **Auto-promote judgment categories** — add ("anything tagged `automation:`
is always in scope"), tighten ("only auto-promote if the user has accepted
>=3 issues of this shape without changes"), or replace the list entirely.
- **Merge conditions** — raise/lower the diff-size thresholds, accept partial
CI red ("if only the integration-test job is red, treat as green"), add new
pre-merge probes ("before merging anything touching `migrations/`, also
require a passing `shelbi db dry-run`"), or change which conditions are
strict vs warnings.The default ships with these categories and conditions filled in. Edit the sections in place to reshape them.
Review loading is also daemon-automated
Where the review-workspace gate above says to "leave it in the review status
for the human to load from the sidebar," the substantive guarantees still
hold: Zen never auto-merges a review-routed issue, the human still resolves it
(accept → done, or bounce → ready), and there is genuinely no shelbi review command. What has changed is who triggers the load: the daemon now
auto-loads queued review-column issues onto idle
review workspaces each poller tick
(autoload_review_queue), so loading is no longer a human-only sidebar
action. The sidebar confirm popup is the manual path for loading a queued
issue onto a chosen slot.
Pre-merge review is the orchestrator's own
Step 5 of the merge conditions is a self-review: the orchestrator reads
the issue's acceptance criteria and the branch diff itself and confirms the
branch does what the issue asked before opening a PR. It does not spawn a
separate evaluator subagent for this. The "don't let the builder grade its
own work" worry does not apply, because the orchestrator is not the builder:
a workspace developer agent wrote the code, and the
orchestrator already owns the review column under Zen and holds the issue's
full context. A green mechanical probe proves the branch builds and does not
conflict; the self-review is what proves it satisfies the issue's intent and
stays in scope. On failure the orchestrator bounces the issue back to ready
with findings (reason=zen:review-reject).
If you want a genuinely independent, de-biased reviewer rather than the
orchestrator reviewing its own dispatch, that is a first-class
qa/adversarial agent
wired into the workflow as a dedicated status, not an ad-hoc subagent.
Give the status owner: agent and agent: qa (or adversarial-review), with
a pass edge forward and a plain bounce edge back to in-progress:
# workflows/task.yaml
statuses:
- { id: in-progress, owner: agent, agent: developer }
- { id: qa-review, owner: agent, agent: qa } # the gate
- { id: review, owner: user, agent: orchestrator }
transitions:
- { from: in-progress, to: qa-review, actions: [push_branch, open_pr] }
- { from: qa-review, to: review, actions: [] } # pass: forward
- { from: qa-review, to: in-progress, actions: [] } # bounce: backThe full walkthrough (declaring the status in
statuses.yaml, referencing it from the
workflow, and why the bounce edge must be
listed) is in
Add it to a workflow.
Relationship to the orchestrator prompt
The judgment categories and merge conditions live in zenmode.md, not in
the orchestrator's prompt. The ## Zen Mode section of
agents/orchestrator/instructions.md tells the orchestrator to treat
zenmode.md as the source of truth and to re-read it on the heartbeat cues
above. So
edit zenmode.md to tune Zen policy, not the prompt. The
orchestrator instructions
remain yours to customize for how the orchestrator reacts to events,
but the Zen policy itself belongs in zenmode.md.
A starter override that tightens Zen's judgment, edited directly in
zenmode.md:
Zen: auto-merge docs/content and automation-tagged issues; hold everything else for me.
## Auto-promote judgment categories
Promote a candidate only if **at least one** is true:
1. **Tagged `automation:` in the title.** Always in scope: these are
bot-filed PR-bumps and lint sweeps we have years of acceptance
history on.
2. **Touches only `docs/**` or `site/content/**`.** Content edits are
reversible and never reach prod.
3. **The user has accepted ≥3 issues of the same shape (same
verb-prefix, same target directory) without changes in the last
week.** Track this against the `done` column.
## Merge conditions
(Inherit from the default; see the bundled template.)Note the first line changed to match the tighter policy. That new summary is what the heartbeat now re-injects.
Other places to tune, all in the same file:
- Raise/lower the diff thresholds: change
files ≤ 30to whatever fits the project's typical PR size. - Accept partial CI red: "if only the integration-test job is red, treat as green" is a common project-specific carve-out.
- Add pre-merge probes: "before merging anything touching
migrations/, also require a passingshelbi db dry-run." - Replace categories entirely with a project-specific taxonomy.
The Rust side never parses any of this, so the prose is the source of
truth. That is the whole design: Zen's policy is one markdown file you
own. For the CLI that flips Zen on and off, see
shelbi zen.
What Zen does: two paths
Zen Mode does exactly two things the default scheduler doesn't:
- Auto-merge: when an issue is queued for a workflow transition
whose
actions:includemerge, Zen runs the high-confidence bar below. If everything passes, it lands the reviewed head on the exact probed base and moves the issue to the transition'stostatus. - Auto-promote: when a workspace frees up, Zen scans the backlog
for issues that are mechanically eligible (see
shelbi zen scan) and then applies the judgment categories inzenmode.mdto decide which ones to promote into todo.
The mechanical layer is in Rust. The judgment layer is in
zenmode.md, which means you own it and can tune it per project. That's the most important point on this page.
The high-confidence bar
The bar fires on any workflow
transition whose actions: list includes merge, not on the
Review column per se. The canonical default workflow happens to put
merge on the Review → Done edge, so in practice an issue that hands off
to review trips the bar. But a
trunk-based workflow that skips Review entirely and merges straight
from InProgress → Done runs through the same probe. Gating is
action-based, not status-pair-based. See What fires Zen Mode's
high-confidence bar.
When such a transition is queued, the orchestrator runs
shelbi zen probe <task-id> (which threads the issue's workflow
through probe_in_workflow so per-workflow overrides take effect; see
below) to get a single JSON report covering every dimension below.
Then it applies the conditions in order. Any failure leaves the issue
where it was, with a tagged reason event so the activity feed shows
what happened.
merge-transition -> probe -> local checks -> merge conflict -> diff size
-> danger paths -> exact-provenance PR flow -> landingThe conditions, in order:
- All
local_checkspass (exit 0). These are the commands you listed underzen.checks.localin the project YAML, or under the workflow's ownzen.checksblock when one is set (see per-workflow overrides). - No merge conflicts with the transition's target branch (the
workflow's resolved
base_branch, or a per-transitiontarget:if one is set). - Diff size within bounds: by default
files ≤ 30andlines_added + lines_removed ≤ 2000. Tunable inzenmode.md. - No danger-path matches: anything matching the resolved
danger-paths list (built-ins + detected shape + project
zen.danger_paths.extend+ the active workflow'szen.danger_pathsoverride, if any) bails out. - PR opens cleanly via
shelbi zen pr-create <id> --match-repository <repository> --match-repository-id <repository_id> --match-base-branch <base_branch> --match-base-commit <base_sha> --match-integration-commit <integration_sha> --match-head-commit <head_sha>. All six values are copied directly from the probe report. When the probe rebased the branch, also pass--match-published-head-commit <published_head_sha>. The PR is returned only after its remote identity matches the exact repository, resolved workflow base name and commit, issue branch, reviewed head, and prebuilt integration commit. The PR head is the integration commit so CI runs on the exact object eligible to land. - CI is green:
shelbi zen ci-watch <pr> --match-repository <repository> --match-repository-id <repository_id> --match-base-branch <base_branch> --match-base-commit <base_sha> --match-integration-commit <integration_sha> --match-head-commit <head_sha> --task <id>polls the PR number returned by that creation step, using the same complete probe identity. One GraphQL response per poll binds the repository, base, PR head, latest commit, required contexts, their results, and GitHub's merge state. A blocked state prevents a not-yet-reported required context from being mistaken for no check, while an optional failed check does not override passing required checks. The--taskflag resolves the timeout against the issue's workflow (zen.ci_timeoutoverride, then the project'szen.ci_timeout, default 900 seconds); plain--timeout <duration>still overrides explicitly. Anything red or a timeout bails. Each poll compares against the frozen probe identity and grades only that atomic snapshot, so persistent movement and a brief A-to-B-to-A change both bail instead of authorizing a merge. A snapshot that would require pagination also fails closed. - Landing. With CI green, the orchestrator finalizes by moving the issue
to the transition's
tostatus (shelbi issue move <id> --to done). That move runs the transition's ownmergeaction (a squash into the resolvedbase_branch, viagh pr mergewhen a PR is open), which is the single merge. The default policy then adds a changelog entry withshelbi changelog add. The exact-provenanceshelbi zen pr-mergeprimitive (sole parentbase_sha, exacthead_shatree, a--force-with-leaseadvance ofrefs/heads/<base_branch>that fails closed on protected branches, forks, merge queues, and non-squash strategies) is reserved for release PRs (version bumps, tap formulas) that noreview -> donetransition governs. Running it in the normal finalize double-merges, since the transition then finds the branch already merged.
The probe's repository, repository_id, base_branch, base_sha,
integration_sha, and head_sha form the contract for the whole sequence. They are not recomputed
between steps: PR creation publishes into that repository and base, and CI grades
that identity continuously. A
workflow, origin, base, or head change requires a fresh probe and a new flow.
Each step uses a single-purpose CLI primitive. The Rust side does
mechanical I/O; zenmode.md holds the policy. That
separation is what lets you raise/lower the bar by editing prose
instead of recompiling.
Example project-level config:
# <project>.yaml
zen:
checks:
local:
- 'cargo test --workspace'
- 'cargo clippy --workspace --all-targets -- -D warnings'
ci_timeout: 900 # seconds (15m)
danger_paths:
extend:
- 'site/public/install.sh'
- 'crates/shelbi-state/src/migrations/**'Per-workflow overrides
Each workflow may carry its own zen:
block that overrides any subset of the three project-level Zen knobs:
checks, ci_timeout, danger_paths. Anything you don't set falls
back to the project default, so a workflow can swap just its
checks without restating the rest.
# ~/.shelbi/projects/<project>/workflows/research.yaml
name: research
description: Long-running investigations, no code-style checks, longer CI.
# References statuses by id from statuses.yaml (drafting is declared there
# with name: Drafting, category: active).
statuses:
- { id: drafting, owner: agent, agent: developer }
- { id: review, owner: user }
- { id: done, owner: user }
zen:
checks:
local:
- 'pytest -k research'
ci_timeout: 3600 # seconds: 1h, vs the project's 15m
danger_paths:
override:
- 'fixtures/**'Why this exists: a research: workflow doesn't want the project's
cargo clippy -D warnings gate, and an integration-test-heavy
workflow may legitimately need an hour of CI. Pinning those to the
workflow rather than the project lets one project's task
workflow stay strict while another workflow in the same project runs
on relaxed rules.
Resolution rules:
checks: when set on the workflow, the workflow's list replaces the project's list outright. Per-issuechecks_only/checks_additionalstill apply on top (see per-issue overrides).ci_timeout: when set on the workflow, the workflow's value wins.shelbi zen ci-watch <pr> <all-probe-match-flags> --task <id>resolves through this override; without--task, the project default is used.danger_paths: uses the sameextend:vsoverride:shape as the project block.extend:adds to the project's resolved list (built-ins + detected shapes + projectextend).override:replaces the whole resolved list for issues in this workflow.
An empty block (zen: {}) parses but overrides nothing, semantically
identical to omitting the block.
The judgment layer
The high-confidence bar decides whether a finished branch is safe to land. The judgment layer decides which backlog items the orchestrator should promote on its own in the first place.
The Rust side (shelbi zen scan) gives the orchestrator a list of
backlog ids that are mechanically safe: not blocked on
dependencies, not explicitly opted out, no file overlap with anything
currently in flight, and, on a remote tracker such as GitHub, assigned to
you (the authenticated account); an unresolved login yields an empty list.
That list is intentionally generous. It doesn't
know what the user wants.
The orchestrator then applies the judgment categories in zenmode.md.
Three ship as the default. A candidate gets promoted only if at
least one is true:
- It's the kind of work the user generally trusts you with.
Pattern from the
donecolumn: does the user routinely accept issues of this shape without changes? - It's part of fixing an issue the user recently raised. The user mentioned this bug or feature in the last few turns.
- It's part of a larger body of work the user explicitly kicked off. A multi-step refactor, a sweep across a dozen pages: the remaining items in that batch are in scope.
If nothing fits, the candidate stays in backlog and the orchestrator
calls it out in its next reply ("I considered promoting <issue> but
wasn't sure. Want me to?"). It also emits
reason=orchestrator:zen-decline reason-text=<short> so the
activity feed shows what was considered and why.
Per-status automation is declarative
Zen's judgment (which backlog items are in scope, where the confidence
bar sits) is prose in zenmode.md, covered above. But the more basic question of
which statuses the orchestrator is allowed to act on, and which agent it
uses, is not prose at all. It's the
owner and agent fields on
each workflow status.
Two examples:
- "Auto-merge but don't auto-promote." Make the merge-bearing handoff
status
owner: agent, but leaveBacklogasowner: user. The orchestrator lands finished work while your triage queue stays yours. It's twoowner:values. - "Review with QA before a human signs off." Give the
Reviewstatusowner: agent, agent: qa. Theqaagent runs the review pass automatically; a downstreamowner: userstatus holds the final accept. Which agent reviews is data, not a prompt instruction.
Flipping Zen on/off still gates whether the orchestrator exercises this
autonomy at all. But once Zen is on, the workflow YAML (not zenmode.md)
decides per status whether the orchestrator acts and which agent it loads.
zenmode.md holds only the genuinely judgment-shaped policy: scope
and the confidence bar. See
owners and agents for the
field semantics and the four validation rules.
Per-issue overrides
Sometimes a single issue needs different treatment than the project
default: kept on the manual-review path because it's sensitive, or
given an extra check the rest of the project doesn't need. Put a zen:
block in the issue's frontmatter:
---
id: refactor-payment-flow
title: Refactor payment processor adapter
column: backlog
priority: 0
zen:
enabled: false # opt this issue out of Zen entirely
------
id: docs-rewrite-tutorial
title: Rewrite the getting-started tutorial
column: backlog
priority: 0
zen:
checks_additional: # extend the project checks for this issue
- 'cargo test --package shelbi-docs'
------
id: ui-restyle-sidebar
title: Restyle the sidebar pill
column: backlog
priority: 0
zen:
checks_only: # replace project checks for this issue
- 'cd site && npm run lint && npm run build'
---Resolution rules:
enabled: falsekeeps the issue on the manual-review path even with Zen on. (enabled: trueis accepted but only affects the in-flight count inshelbi zen status; it does not make the orchestrator act on the issue while Zen is off.)checks_onlytakes precedence overchecks_additional. If both are set,checks_onlywins andchecks_additionalis ignored.checks_additionalextends whichever list the workflow layer resolved to: workflowzen.checks.localif set, else projectzen.checks.local.- Absent both, the issue inherits that resolved list verbatim. Full
precedence: per-issue
checks_only> per-issuechecks_additional> per-workflowzen.checks.local> projectzen.checks.local.
Configuration reference
Zen Mode reads config from two places: the project YAML (project-wide defaults) and per-workflow YAMLs (workflow-scoped overrides, covered in Per-workflow overrides above).
The full project-level schema:
zen:
# Shell commands run from the worktree root before the merge bar.
# Each must exit 0. Default: empty.
checks:
local:
- 'cargo test --workspace'
- 'cargo clippy --workspace --all-targets'
# How long to wait for required GitHub checks, in seconds (a bare
# integer; strings like `15m` are rejected). Default: 900 (15m).
ci_timeout: 900
# Glob patterns considered too sensitive to auto-merge. Choose
# one of `extend:` (keep built-ins + detected-shape paths and add
# yours) or `override:` (replace everything with your list).
# Default: an empty `extend:` (built-ins + detected shape only).
danger_paths:
extend:
- 'site/public/install.sh'
- 'crates/shelbi-state/src/migrations/**'
# or to replace everything Shelbi knows about:
# override:
# - 'config/**'
# - 'deploy/**'The built-in danger paths Shelbi always includes (in extend mode):
.github/workflows/** scripts/install.sh *.yaml *.yml
LICENSE package-lock.json Cargo.lockOn top of that, Shelbi detects the project shape from sentinel files in your repo root and adds shape-specific paths:
| Shape | Detected from | Adds |
|---|---|---|
| cargo workspace | Cargo.toml with [workspace] | Cargo.toml, Cargo.lock, rust-toolchain.toml, .cargo/config.toml |
| node / next.js | package.json | package.json, package-lock.json, next.config.*, vercel.json, .npmrc |
| github | .github/ | .github/CODEOWNERS, .github/dependabot.yml |
| docker | Dockerfile or compose.yaml | Dockerfile, compose.yaml |
| shelbi | shelbi.yaml or .shelbi/ | .shelbi/**, shelbi.yaml |
Run shelbi zen status to see the resolved list for your project
(it labels the detected shapes inline so you know which contributions
came from where). When any workflow declares its own zen: block, the
status output also lists each one and which dimensions it overrides,
with the resolved per-workflow values printed underneath.
The activity feed
Zen-driven events in the Activity feed show a dim [zen] chip in the
identity column (where workspace chips like [alpha] go), so
machine-driven actions are visually separable from user actions in the
same stream.
The Zen reason strings the orchestrator writes are below. On disk, the
event log folds spaces in a reason into _ (for example
orchestrator:zen-promote_category=2). The feed gives
orchestrator:zen-merge its own merge phrasing (reading the PR from
pr=<n>); the other reasons currently render as a generic move with the
[zen] chip.
| Reason | Meaning |
|---|---|
orchestrator:zen-promote category=<n> | Promoted a backlog issue to todo under judgment category <n>. |
orchestrator:zen-decline reason-text=<short> | Considered a candidate, decided not to promote. |
orchestrator:zen-merge pr=<n> | Auto-merged a reviewed issue to main. |
zen:failed-checks cmd=<…> exit=<…> | Local check failed; issue stays in review. |
zen:diff-too-large files=<…> lines=<…> | Diff exceeded the size threshold. |
zen:danger-path paths=<…> | One or more danger-path globs matched. |
zen:merge-conflict files=<…> | Branch wouldn't merge cleanly. |
zen:ci-timeout duration=<…> | CI didn't settle within ci_timeout. |
These tags also surface in ~/.shelbi/events.log. Grep there for a
full audit trail across all projects.
The orchestrator also wakes on the project's periodic heartbeat line
(see the events log). For Zen
Mode that's the recurring nudge to check in-flight reviews and stuck
auto-promotions when nothing else has fired. The merge bar can spend
minutes waiting on CI, and heartbeats keep the orchestrator from
sleeping through the settle. The cadence comes from the heartbeat
key in project.yaml (default 60s, backing off to a 5m cap while the
board is quiescent).
See also
- Project config: the
zen:block's field reference (checks.local,ci_timeout,danger_paths). shelbi zen: CLI reference for every Zen subcommand.- Workflows: how a workflow's
transitions:declares which edges fire the merge bar, and where the per-workflowzen:override block lives. - Orchestrator: how the underlying scheduler is wired and where the prompt template lives.
- The events log: the canonical
format of the
mode=zenandorchestrator:zen-*lines Zen emits. - GitHub Issues backend: on a GitHub-backed board, Zen only auto-promotes backlog issues assigned to the authenticated user; assign yourself to make an outside issue eligible.
- Workflows:
what
ReviewandDonemean in the default flow Zen accelerates, and how custom workflows declare their ownhandoffanddonestatuses.