A workflow is a YAML file that declares the statuses a task moves through, who owns each one, and what git side-effects fire on each transition. shelbi init ships two: task, the review-gated default (Backlog → Todo → In Progress → Review → Done, plus Canceled), and subtask, a lighter flow for a piece of a parent task that opens no PR and has no review (see Shipped workflows below). Drop another YAML next to them and you have a third pipeline: a docs-only flow, a research pipeline that never opens PRs, a feature-stacking flow whose tasks branch off a long-lived feature branch.

Status names are user-facing labels: what shows up on the Kanban card. Generic code (orchestrator auto-dispatch, Zen Mode's confidence bar, the events log, the activity feed) keeps working unchanged because every status maps to a small, closed set of categories: backlog, ready, active, handoff, done. Categories are the vocabulary the rest of the system reasons in.

The default workflow renders as the canonical five-column board, one column per category, each colored by its category:

jlong@hub — shelbi
Tasks · my-project 34 total Workflow: All ▾ Workspace: All ▾ BACKLOG (10) TO DO (9) IN PROGRESS (4) REVIEW (5) DONE (6) Rework onboarding UX Add API ratelimit Deploy staging env Cold-start cache Migrate to PG 16 app app app ⎇ shelbi/deploy… app ⎇ shelbi/cold-s… app Audit OSS licenses Fix mobile nav Wire up OAuth flow CSV import fix Ship dark mode app app app ⎇ shelbi/wire-u… app ⎇ shelbi/csv-im… app Draft Q3 roadmap Wire webhook retries Backfill order index Nightly report Retry dead-letters app app app ⎇ shelbi/backfi… app ⎇ shelbi/nightl… app Migrate CI to arm64 Split OTel spans Trim vendor bundle Validate webhook Redis cache /profile app app app ⎇ shelbi/trim-v…payloads app app ⎇ shelbi/valida… Sunset legacy v1 API Sync i18n strings Add audit logging app app Harden token refresh app app ⎇ shelbi/harden… Add SSO for admins Paginate search API Fix flaky CI tests app app app Prune stale flags Cache user sessions app app Dedupe error reports Add health probes app app Archive S3 buckets Debounce autosave app app Refresh brand assets app
h/l col j/k row open n new f filter r refresh

Schema and parser live in crates/shelbi-core/src/workflow.rs. Workflow files live at ~/.shelbi/projects/<project>/workflows/<name>.yaml.

Choosing the default workflow

A task can pin a workflow explicitly:

workflow: feature-task

When workflow: is absent, Shelbi checks the project config for default_workflow::

# ~/.shelbi/projects/myapp.yaml or <repo>/.shelbi/project.yaml
default_workflow: task

That makes tasks without workflow: behave as if they declared workflow: task. Fresh projects get default_workflow: task written by the scaffold, so the review-gated task flow is the default out of the box. If the project field is omitted, Shelbi falls back to the built-in default. A configured default must name an existing workflows/<name>.yaml; if it does not, project loading fails with a workflow error instead of silently falling back.

Schema

A workflow is split across two files. Status identity (the stable id, the display name, and the category) is declared once in workflows/statuses.yaml, the project-wide status catalog. Each workflow file then references statuses by id and adds only what's workflow-specific: the owner, and an optional agent:.

# ~/.shelbi/projects/<project>/workflows/statuses.yaml
# The status catalog: identity lives here, once, shared by every workflow.
statuses:
  - { id: backlog,     name: Backlog,     category: backlog }
  - { id: todo,        name: Todo,        category: ready }
  - { id: in-progress, name: In Progress, category: active }
  - { id: review,      name: Review,      category: handoff }
  - { id: done,        name: Done,        category: done }
# ~/.shelbi/projects/<project>/workflows/default.yaml
name: default
description: The standard one-track flow shipped with every project.
 
# Reference statuses by id. name/category come from statuses.yaml and are
# never repeated here — a workflow file adds only owner (+ optional agent).
statuses:
  - { id: backlog,     owner: user,  agent: orchestrator }
  - { id: todo,        owner: agent, agent: orchestrator }
  - { id: in-progress, owner: agent, agent: developer }
  - { id: review,      owner: user,  agent: orchestrator }
  - { id: done,        owner: user }
 
# Optional. Which status a new task lands in. Defaults to the first
# entry in `statuses:` above.
# initial_status: backlog
 
# Optional. Per-workflow override of the project-level `git:` block in
# project.yaml. Most workflows omit it and inherit base_branch/merge_strategy
# unchanged.
# git:
#   base_branch: main
#   merge_strategy: squash         # squash | merge
 
# Optional. Edge-by-edge action declarations. Each entry says what side
# effects to run when a task crosses that edge. Omitted edges run no
# actions — pure status moves. Transitions are any-to-any: this block
# does NOT restrict which moves are legal.
# transitions:
#   - from: in-progress
#     to: review
#     actions: [push_branch, open_pr]
#   - from: review
#     to: done
#     actions: [merge, delete_branch]

Why two files? A status's identity is project-wide, but its automation differs per workflow. The same review status can be owned by a human in one workflow and handled by a qa agent in another. Declaring name and category once in statuses.yaml keeps those facts from drifting between workflow files; the loader rejects a workflow that repeats name: or category: inline, or that references an id the catalog doesn't declare.

The required fields are name and statuses (at least one). Everything else is optional. A workflow without transitions: is a pure bookkeeping workflow: no git side-effects fire on any move.

Validation runs at load time and catches status ids not declared in statuses.yaml, duplicate status ids, initial_status pointing at nothing, transition edges referencing undeclared statuses, and the owner/agent rules covered below. The error message names the workflow and the bad field.

Status categories

Every status, default or custom, declares one of five categories:

CategorySemanticExample status names
backlogNot yet ready for work — triage stage.Backlog, Inbox, Triage
readyReady for whoever owns it to pick up.Todo, Ready, Next Up
activeOwner is working on it now.InProgress, Building, Designing, Drafting
handoffOne owner has finished their part; another's input is required.Review, QA, Awaiting Sign-off
doneTerminal — accepted, shipped.Done, Shipped, Released

The category set is closed at five. A workflow can't introduce a sixth. Generic code (Zen Mode, activity feed, orchestrator routing) keys off the category, and an unknown one would have nowhere to fit. A workflow can repeat a category, though: a long pipeline might have three active statuses (Design, Build, QA) with different owners.

Categories let the rest of the system reason without caring about names:

  • The orchestrator dispatches ready-category statuses whose owner is agent. It doesn't care whether you called the column Todo or Ready or Next Up.
  • The hub poller's review-marker promotion lands a task in the next handoff status: the same code path for Review, QA, Awaiting Sign-off.
  • The activity feed renders an active → handoff move as "handed off" regardless of the actual status names involved.

The category for each status appears on every events log line so consumers can match on the semantic without looking up the workflow. See events log line shape.

Owners and agents

Each status declares two things about who handles it, one required, one optional:

# workflow file — reference-only; name/category come from statuses.yaml
statuses:
  - { id: todo,        owner: agent, agent: orchestrator }
  - { id: in-progress, owner: agent, agent: developer }
  - { id: review,      owner: agent, agent: qa }
  - { id: signoff,     owner: user }
  - { id: done,        owner: user }

owner: user | agent declares who the next mover is:

  • agent — the orchestrator is the next mover. Tasks sitting in an agent-owned ready status are eligible for auto-dispatch onto a free workspace.
  • user — the orchestrator does not act. The task waits for you to move it, surfaced in the activity feed so you know your attention is requested.

There's no third "either": a status is either work the orchestrator will pick up or work that waits for a human. If you want to grab an agent-owned task by hand, reassign it through the normal CLI/TUI; that's an action, not a schema field.

agent: <agent-name> names which agent role does the work, when owner: agent. Omit it and Shelbi derives a default from the category (developer for active, orchestrator for ready). Name one (a shipped reviewer like qa, security, or adversarial, or any agent you've authored) and the orchestrator loads that role into the workspace it dispatches to. The Review status above is auto-handled by the qa agent before any human is involved; that's how "this status is reviewed by QA" becomes a line of YAML instead of a paragraph of orchestrator prompt.

Validation rules

The loader enforces these rules, failing the load with a message that names the workflow and status:

  1. Every id must be declared in statuses.yaml. A workflow references statuses by id; an id the catalog doesn't know about (a typo, a status you forgot to add) fails the load, and the error lists the available ids. Repeating an inline name: or category: in a workflow file is rejected too. Identity lives in the catalog, so the two files can't drift.
  2. owner is user or agent. No other value parses, and a missing owner is an error too. There's no implicit default.
  3. An agent-owned status must name its agent:. For the ready and active categories a bare owner: agent is accepted and defaults to orchestrator / developer (with a deprecation warning); any other category with owner: agent and no agent: fails the load.
  4. A user-owned status may still name an agent:. That declares "under Zen Mode this agent may act without me". It's how the shipped default lets the orchestrator handle Backlog and Review under automation. Terminal statuses (Done, Canceled) simply omit it: no automation path at all.

Zen as declarative data

Because the owner/agent pair lives in the workflow YAML, per-status automation is data, not prompt prose. Whether the orchestrator acts at a given edge is the owner field; which agent it uses is the agent field. That makes each of these choices a YAML edit:

  • "Auto-merge but don't auto-promote." Keep Backlog as owner: user (you still triage), but make the merge-bearing handoff status owner: agent. The orchestrator lands finished work without touching your triage queue. No prompt surgery.
  • "Review with QA, sign off by hand." Split the handoff into a Review status (owner: agent, agent: qa) and a Signoff status (owner: user), as in the example above. The QA pass is automated; the final accept stays human.

The judgment policy (which backlog items are in scope to promote, and the confidence bar a merge must clear) still lives in the orchestrator's prompt, because it's a judgment call. But the structural "is this status automated, and by whom" question is now answered declaratively in the workflow. See Zen Mode.

Transitions

The transitions: block declares side-effects, not legal moves. Cards can be dragged between any two declared statuses; what transitions: controls is what happens on the way. An undeclared edge is a pure status change with no git activity.

Each transition lists actions in execution order. Failures short-circuit the rest: if merge fails, delete_branch does not run.

ActionEffect
push_branchPush the task's branch to origin. No-op if already up to date.
open_prOpen a PR. Base is transition.target if set, else the workflow's resolved base_branch, else the parent task's branch when depends_on is set. No-op if a PR is already open.
mergeMerge the task's branch with the effective merge_strategy. Via gh pr merge when a PR is open; via local git merge on the hub otherwise. v1 strategies: squash, merge.
close_prClose any open PR without merging.
delete_branchDelete the local + remote branch. Skipped if a workspace still has the branch checked out. That worktree replaces the branch on its next dispatch.
restackRebase the task's branch onto its parent task's current branch. Fires automatically on dependent tasks when a parent's merge completes (see depends_on).

All actions are hub-side and idempotent. The orchestrator (or the CLI) on the hub machine runs them; workspaces never invoke actions directly.

The canonical default workflow's transitions, written out:

transitions:
  - from: in-progress
    to: review
    actions: [push_branch, open_pr]
 
  - from: review
    to: done
    actions: [merge, delete_branch]

Per-transition target:

Each transition may declare a target: field that overrides where merge and open_pr land for that edge only:

transitions:
  - from: in-progress
    to: review
    target: develop                # merge feature work into develop here
    actions: [push_branch, merge]
 
  - from: review
    to: done
    # no target → uses the workflow's base_branch (e.g., main)
    actions: [open_pr, merge]

target: does not affect where new task branches are cut from. That's always the resolved base_branch. It only controls where merges land for this particular hop.

What fires Zen Mode's high-confidence bar

Zen Mode gates any transition whose actions include merge, regardless of which statuses sit on either side. A trunk-based workflow that skips Review and goes straight Active → Done with actions: [merge, delete_branch] trips the same probe as the canonical Review → Done. That's the whole point of expressing side-effects as actions: the gate is action-based, not status-pair-based.

The branch: task field

Every task carries an optional branch: field in its frontmatter: the name of the git branch the task operates on. Its presence at creation controls whether Shelbi cuts a fresh branch or uses an existing one.

---
id: build-login-form
title: Build the login form
workflow: default
# branch:  ← omitted → orchestrator generates jlong/build-login-form on dispatch
---
  • Omitted at creation. When the task moves into its first active-category status (typically InProgress), the orchestrator cuts a fresh branch off the workflow's resolved base_branch, names it by rendering the workflow's (or project's) git.branch template (the shipped task workflow uses {{github_user}}/{{id}}, so build-login-form becomes jlong/build-login-form), and writes the name back into the task's frontmatter. When no branch template is configured, Shelbi falls back to your GitHub username. The user never types a branch name. This is the common case.
  • Set at creation. The orchestrator uses that branch as-is. No new branch is cut. This is how release tasks work: the user already knows the branch (typically feature/<name>) and pre-fills it.
---
id: ship-auth-rewrite
title: Ship the auth rewrite to main
workflow: feature-release
branch: feature/auth-rewrite       # pre-existing — use this branch as-is
---

Once populated, branch: is the source of truth for every subsequent action: push_branch, open_pr, merge, and delete_branch all operate on it. Workflows don't carry a separate "what branch to ship" field; the task does.

Parameterization

A git: block can use {{var}} placeholders that resolve from the task's frontmatter at load time. This is what makes one feature-task.yaml reusable across any number of concurrent features:

# workflows/feature-task.yaml
name: feature-task
required_params: [feature]             # every task must set feature:
git:
  base_branch: feature/{{feature}}     # resolved per task
  merge_strategy: squash
 
statuses:
  - { id: todo,        owner: agent, agent: orchestrator }
  - { id: in-progress, owner: agent, agent: developer }
  - { id: review,      owner: user }
  - { id: done,        owner: user }
 
transitions:
  - from: in-progress
    to: review
    actions: [push_branch]             # push so reviewers can see; no PR
  - from: review
    to: done
    actions: [merge, delete_branch]    # merge into feature/{{feature}}

Two tasks, two features, one workflow file:

---
id: build-login-form
title: Build the login form
workflow: feature-task
feature: auth-rewrite            # base_branch resolves to feature/auth-rewrite
---
---
id: wire-dashboard-shell
title: Wire the dashboard shell
workflow: feature-task
feature: dashboard-v2            # base_branch resolves to feature/dashboard-v2
---

Substitution rules:

  • Plain string replacement. No conditionals, no expressions, no defaults.
  • Placeholders are allowed in git: field values only. They are not resolved inside status names, category names, or the transitions: shape.
  • A missing key produces one clear error per workflow listing every unresolved placeholder, so a single edit to the task's frontmatter fixes them all: "workflow feature-task requires task params: feature".
  • Every {{var}} in base_branch must be declared in the workflow's required_params. Shelbi rejects the workflow at load time otherwise, so a base branch that references a field no task carries is caught before dispatch, not after.

Dependent tasks with depends_on

Tasks declare a depends_on: list in their frontmatter to express a parent/child relationship. This is how Graphite-style stacked PRs are modeled: task B builds on top of task A's branch, which builds on main.

---
id: build-login-form
title: Build the login form
workflow: default
---
---
id: build-dashboard
title: Build the dashboard (uses login form)
workflow: default
depends_on: [build-login-form]   # branch cut off build-login-form's branch
---

Three behaviors fire when depends_on: is set:

  1. Blocked while waiting. A task is blocked from auto-dispatch while any id in depends_on is not yet in a done-category status. The Kanban renders blocked cards with a 🔒 badge; they sit in the ready-category column until their dependencies clear. This is the same gate the default workflow's Todo status applies to dependent tasks. See the lifecycle walkthrough above.
  2. Branch cut from the parent. When the dependent task transitions into its first active-category status, the orchestrator cuts its branch off the parent task's branch: (not the workflow's base_branch). The child branch is still generated as <prefix>/<child-task-id>.
  3. PR base is the parent. When the dependent task's open_pr action runs, the PR's base is the parent task's branch, not the workflow's base_branch.

A fourth behavior fires on the parent:

  1. Auto-restack on parent merge. When the parent's merge action completes, the orchestrator iterates every task with depends_on: containing the parent's id and runs the restack action on each. The child branch is rebased onto the parent's target (typically main), and any open PR's base is updated to match. A child with multiple parents waits until every parent is in a done-category status, then restacks once onto the converged target. Chains restack transitively.

Cycles in depends_on: are rejected at task-load time with a clear error naming the offending task and the cycle path. A task whose parent ended up in a non-done terminal state (cancelled, won't fix) is surfaced in the activity feed and won't proceed past the ready-category column until the user resolves it.

Per-task workflow assignment

Tasks declare which workflow they belong to in frontmatter:

---
id: design-the-thing
title: Design the thing
workflow: design-review         # optional; otherwise project default, then "default"
column: Designing               # uses a status name from the assigned workflow
---

If workflow: is absent, the task uses default_workflow: from project config. If the project does not declare one, Shelbi falls back to default. If an explicit task workflow: references a missing definition, Shelbi falls back to the built-in default; a missing configured project default fails project loading because that would otherwise reroute every implicit task silently.

A task is bound to its workflow at creation. Cross-workflow moves are not allowed. To switch a task into a different workflow, create a fresh task in the target workflow (and archive the original if you want).

Shipped workflows: task and subtask

Every new project ships two workflow files, and default_workflow: task is written into the project config so task is the default.

task

workflows/task.yaml is the review-gated default: a task branches off main as <your-github-user>/<id> (the shipped branch: '{{github_user}}/{{id}}' template), moves through Backlog → Todo → In Progress → Review → Done (with a Canceled archive status), and squash-merges to main on accept. It opens exactly one PR, on In Progress → Review, which is both the review surface and, once accepted, what lands on main.

# workflows/task.yaml — references statuses.yaml (see Schema above)
name: task
description: "Branch work with a review gate: the default track shipped with every project."
 
git:
  base_branch: main
  branch: '{{github_user}}/{{id}}'
  merge_strategy: squash
 
statuses:
  - { id: backlog,     owner: user,  agent: orchestrator }
  - { id: todo,        owner: agent, agent: orchestrator }
  - { id: in-progress, owner: agent, agent: developer }
  - { id: review,      owner: user,  agent: review, tags: [review] }
  - { id: done,        owner: user }
  - { id: canceled,    owner: user }
 
transitions:
  - { from: in-progress, to: review,   actions: [push_branch, open_pr] }
  - { from: review,      to: done,     actions: [merge, delete_branch] }
  - { from: in-progress, to: canceled, actions: [close_pr, delete_branch] }
  - { from: review,      to: canceled, actions: [close_pr, delete_branch] }

The Review status is where the review workspace comes in. It's owner: user (you still own the accept), but it names agent: review and carries tags: [review]. When a task lands here, the orchestrator dispatches it to a review-tagged workspace and loads the Reviewer agent, which stands up the running app so you can click through the branch before you merge. Declare a review-tagged workspace slot on the hub for exactly this (add tags: [review] to a workspace in the project YAML). See Review workspaces for the tag-matching rules and how the dev server is booted.

To put a shipped reviewer on the board, name it on a status's agent: field. The three reviewer presets (qa, security, adversarial) ship materialized but unwired, so wiring one in is a one-line edit: point a status at agent: qa (or security, or adversarial). Doing more with agents walks a full example, including adding a dedicated review column and its transitions.

subtask

workflows/subtask.yaml is for a piece of a parent task, done on its own branch with no PR and no review. A subtask names its parent task's id in its task: frontmatter, and the templated base_branch: task/{{task}} resolves to the parent's branch, so the subtask branches from and squash-merges directly into that branch, never main. Its work surfaces only on the parent task's single PR. It's handoff-less by design: it declares no handoff status, so the hub poller auto-advances a finished worker straight to Done, firing the merge into the parent branch.

# workflows/subtask.yaml
name: subtask
description: A piece of a parent task, squash-merged into the parent's branch with no PR or review.
 
required_params: [task]           # every subtask must name its parent task
git:
  base_branch: task/{{task}}      # resolves to the parent task's branch
  branch: 'subtask/{{id}}'
  merge_strategy: squash
 
statuses:
  - { id: backlog,     owner: user,  agent: orchestrator }
  - { id: todo,        owner: agent, agent: orchestrator }
  - { id: in-progress, owner: agent, agent: developer }
  - { id: done,        owner: user }
  - { id: canceled,    owner: user }
 
transitions:
  - { from: in-progress, to: done,     actions: [merge, delete_branch] }
  - { from: in-progress, to: canceled, actions: [delete_branch] }

Shipping these as editable files makes the default flow a thing you can read and change rather than implicit behavior. You can rename Todo to Ready in statuses.yaml without breaking anything, since the rest of the system reasons in categories.

The built-in default fallback

The default workflow name is a built-in fallback. If a project has no workflows/ directory at all, or a task names a workflow: that can't be found, Shelbi falls back to the built-in default. A task with no workflow: field belongs to the project default workflow (task on a freshly scaffolded project), or default when the project configures none.

Lifecycle: who moves a task between the default statuses

Every status declares an owner, and the owner of the next status is who picks up the move. That makes the lifecycle of a task in the default workflow legible at a glance:

┌──────────┐   ┌──────┐   ┌─────────────┐   ┌────────┐   ┌──────┐
│ Backlog  │ → │ Todo │ → │ InProgress  │ → │ Review │ → │ Done │
└──────────┘   └──────┘   └─────────────┘   └────────┘   └──────┘
   you            you      orchestrator        poller      you
   (triage)       (promote)  (auto-dispatch)   (marker)   (accept)
  • Backlog → Todo — you. The orchestrator drops anything it creates from a natural-language request into Backlog. You decide what's worth doing now, and in what order. Order within Todo is your priority list; the orchestrator dispatches from the top.
  • Todo → InProgress — orchestrator. As soon as a card sits in Todo (or a workspace frees up), the orchestrator routes it to a free workspace. This is the auto-dispatch loop. A task is blocked and skipped while any id in depends_on: is not yet in a done-category status. The Kanban shows blocked cards with a 🔒 badge.
  • InProgress → Review — hub poller. Promoted automatically the moment the workspace writes its review-ready marker (see next subsection). No shelbi command on the workspace, no orchestrator participation.
  • Review → Done — you. Accepting a diff is the moment the human signs off on the work. The orchestrator never crosses this edge on its own (except under specific multi-task loops the user has authorized, typically via Zen Mode's auto-merge).

The pattern generalizes: you own the intent boundaries (triage and accept), the system handles the mechanical middle. A custom workflow that swaps Review for QA + Awaiting Sign-off doesn't change the principle: anywhere owner: user sits, the orchestrator waits for you.

Review-marker promotion

The active → handoff transition (InProgress → Review in the default workflow) doesn't need any active participant. The workspace doesn't run a shelbi command. The orchestrator doesn't watch the workspace's pane. Instead:

  1. The workspace, when done, writes its task id into <worktree>/.claude/shelbi-ready. This instruction is part of the initial prompt every task ships with (see compose_prompt in crates/shelbi-orchestrator/src/workspace.rs).
  2. The hub poller cats that file on every tick (locally for hub workspaces, via SSH for remote ones).
  3. If the file is non-empty and names a task that's in an active-category status for this workspace, the poller moves the task into the next handoff status, clears the marker, and appends task=<id> <from> -> <to> reason=workspace:ready-marker to the events log.

The orchestrator hears about it through its shelbi events tail --follow feed and dispatches the next ready task to the now-free workspace.

That this works without the workspace holding any Shelbi-specific state is the point. The marker is the entire on-workspace protocol. See the workspaces concept page for the rationale and the reason strings reference for the full set of tokens.

A worked example: stacked feature branches

The full picture: two workflows working together to ship a multi-task feature.

workflows/feature-task.yaml:

name: feature-task
description: Stack tasks on a long-lived feature branch.
 
required_params: [feature]            # every task must set feature:
git:
  base_branch: feature/{{feature}}     # supplied per task
 
statuses:
  - { id: todo,        owner: agent, agent: orchestrator }
  - { id: in-progress, owner: agent, agent: developer }
  - { id: review,      owner: user }
  - { id: done,        owner: user }
 
transitions:
  - from: in-progress
    to: review
    actions: [push_branch]             # push so reviewers see it; no PR
  - from: review
    to: done
    actions: [merge, delete_branch]    # squash into feature/{{feature}}

workflows/feature-release.yaml:

name: feature-release
description: Ship a long-lived feature branch into main.
 
# Inherits project default base_branch: main, merge_strategy: squash —
# no git: block needed.
 
statuses:
  - { id: todo,        owner: agent, agent: orchestrator }
  - { id: in-progress, owner: agent, agent: developer }
  - { id: review,      owner: user }
  - { id: done,        owner: user }
 
transitions:
  - from: in-progress
    to: review
    actions: [push_branch, open_pr]    # PR the existing feature branch
  - from: review
    to: done
    actions: [merge, delete_branch]    # squash into main, delete branch

Now tasks in feature-task carry feature: auth-rewrite in their frontmatter, so feature-task's base_branch resolves to feature/auth-rewrite, and the orchestrator cuts each task branch off it at dispatch. A second feature's tasks carry feature: dashboard-v2. Same workflow file, two concurrent stacks.

When auth-rewrite is ready to ship, create a task in feature-release with branch: feature/auth-rewrite pre-filled. Because branch: is set, the orchestrator skips the "cut a new branch" step and operates directly on the feature branch. One PR-and-merge cycle lands the whole feature into main.

CLI reference

  • shelbi workflow list — list every workflow in the project.
  • shelbi workflow show <name> — print the resolved YAML.
  • shelbi workflow new <name> — scaffold a starter file.
  • shelbi workflow edit <name> — open in $EDITOR.
  • shelbi task add "Title" [--workflow <name>] — create a card, optionally in a non-default workflow.
  • shelbi task move <id> --to <status> — validates that <status> is a member of the task's workflow; errors with a list of valid options on mismatch.
  • shelbi task list [--workflow <name>] — filter by workflow.

See also

  • Workflow config and Statuses: the field-by-field YAML reference for the files this page explains conceptually.
  • Workspaces — what's on the receiving end of an active-category dispatch, and the review-marker mechanism workspaces use to hand off.
  • Agents — the role the agent: field names.
  • Orchestrator — how ready-category statuses get dispatched.
  • The events log — line shape for task transitions, including the workflow + category annotations.
  • Zen Mode — what the action-based confidence bar gates, and how per-workflow zen: overrides work.