Workflow
How items move through Joy's six statuses, the verb shortcuts, how gates scale strictness from solo to team, and how jobs assign work for execution.
Every Joy item moves through one small, fixed set of statuses. There is exactly one workflow per project: it is not templated, selectable, or importable. You scale strictness by tightening individual transitions with gates, not by adding statuses.
The states
- new: the item exists or was proposed, but is not yet approved for work.
- open: approved and triaged into the backlog, ready to pick up.
- in-progress: being worked on.
- review: work done, awaiting acceptance.
- closed: accepted and finished.
- deferred: deliberately set aside; reachable from any active state, because there can always be a reason to put something on hold.
blocked is not a manual state; it is computed automatically from dependencies.
The verbs
Each transition has a one-word shortcut:
joy approve IT-0001 # new -> open
joy start IT-0001 # new or open -> in-progress
joy stop IT-0001 # in-progress -> open
joy submit IT-0001 # in-progress -> review
joy close IT-0001 # review -> closed
joy rework IT-0001 # review -> in-progress
joy defer IT-0001 # any active state -> deferred
joy reopen IT-0001 # closed or deferred -> openstop is the counterpart of start: it puts an in-progress item back into the backlog. On a running job it also aborts the current execution.
Gates
By default every transition is open and Joy only warns, never blocks. When you need more control, you tighten individual transitions with gates. The workflow itself stays the same.
start works directly from new, so the open status is optional in everyday use. It comes into play with a triage gate on new -> open: items are then approved into the backlog with joy approve before work starts. A gate with allow_ai: false on new -> open is a handy way to keep AI-created items in new until a human approves them.
Solo: no gates
Every transition is open; start goes straight from new.
Team: triage and acceptance gates
A triage gate on new -> open (with allow_ai: false for AI-created items) routes proposals through human approval; an acceptance gate on review -> closed lets only leads close work, optionally requiring green CI.
Same workflow, more strictness: nothing was added to the state model, two transitions were tightened. See Capabilities and gates for how gates are configured.
One workflow, tuned with gates
Joy keeps a single, deliberately small set of statuses and lets you adjust strictness through gates rather than by adding states or selectable processes.
Decisions: the validity axis
A decision item records an architectural or product decision. Decisions are the project's living specification: people and AI read them before any work to learn the rules that bind it. They are not a backlog to burn down.
A decision flows through the normal workflow while it is being made (new to closed). But "is the deciding finished?" and "does the decided rule still hold?" are two different questions, so a decision carries a second field, validity, orthogonal to status:
statustracks the work of deciding.closedmeans the deciding is finished.validitytracks the rule that came out of it.
A decision binds when its status is closed and its validity is accepted. Closing the decision is the precondition for the rule to take force, just as finishing construction is the precondition for a building to open.
| validity | meaning |
|---|---|
accepted | in force, a binding rule |
proposed | still being decided, not yet binding |
rejected | considered and declined, never in force |
replaced | superseded by another decision (see replaced_by) |
retired | dropped without a successor, no longer in force |
Closing a decision defaults its validity to accepted. A decision is never deleted; when it is superseded you record the successor with joy edit IT-0001 --replaced-by IT-0042, which links the two and marks the old one replaced.
Reading decisions
The -D / --decisions view is built for reading the living specification:
joy ls -D # decisions only, with a val (validity) column
joy ls -D -a # include closed and retired ones too
joy board -D # a board grouped by validity, not statusTwo axes, one item
status answers "is the deciding done?"; validity answers "does the rule still hold?". A binding decision is closed and accepted; a closed decision that was replaced is history, not current policy.
Jobs: assigning work
A job item is an assignment of work over a scope of items, executed by an assignee - an AI or a human - once approved. Where a story describes what to build, a job says who executes which slice of the backlog.
A job is created like any other item; the one difference is a third positional that only jobs accept and require - the comma-separated scope:
joy add job "Implement recipe search" JOY-0001,JOY-0002A scope entry that is a container (an epic or a milestone-scoped item) means its whole subtree. Scope is a field of its own, not dependencies: deps on a job express job ordering (job B after job A) and drive the computed blocked state as usual.
Jobs in the workflow
Jobs move through the same six statuses with the same verbs; the meanings just sharpen:
- new: the job is proposed - also by an AI.
- approve is the triage gate: approving a job authorizes its execution and its spend. For AI members this gate is closed by default.
- start: on the platform this hands the job to the assignee and launches execution.
- stop: aborts the current execution; the attempt is recorded with the cost that actually accrued. It does not revoke the approval - use
deferfor that. - review: the result awaits acceptance. A job whose execution finally failed also lands in review, carrying the error.
- close / rework / defer: as for any item.
Each execution loop is recorded as an attempt on the job: started, ended, outcome (succeeded, failed, aborted), tokens, cost, branch, and the result or error. Retries are attempts, not extra statuses - a job being retried simply stays in-progress. A job can also carry a budget (max cost, max tokens) and a window (not-before, deadline).
Job gates are governed by their own capability, jobs, deliberately separate from the item review capability: approving a job authorizes spend, reviewing a job accepts the delivered work, and the person accepting AI work and the person accepting the product item can differ.
Where jobs live
Jobs are stored in .joy/jobs/ (not .joy/items/) with IDs like JOY-JOB-0001-<hash> in their own number space. They never appear in default views:
joy ls -J # open jobs
joy ls -Ja # include closed ones too
joy board -J # the job boardDeleting .joy/jobs/ leaves the product backlog untouched.
Closing a job
Closing a job does not auto-close its scope items. joy close <JOB> asks per item (Close JOY-0001 "..."? [y/N]); scripts use --items or --no-items instead.
Two spaces, one workflow
Items describe the product; jobs describe who executes which slice of it, at what cost. Same statuses, same verbs, separate number space.