Docs menu

Architecture Process lifecycle

Process lifecycle

A process is a lane that a ticket moves through, from the issue to the merged change. This page explains lanes, the kinds of step, gates and how a ticket ends.

The example lane below is the sample process, extended with a security scan and a deploy. A ticket moves through it from the first issue to the merge.

GitHub issuenew ticket
→
agentplan
→
human gatespec_review_gate
→
agentbuild
→
agenttest
→
human gatecode_review_gate
→
checksecurity_scan
→
CI/CDdeploy to prod
no labels yet A product owner opens issue #142, "Rate-limit the login endpoint", and describes what they need. 1 / 15

Lanes

A process is one lane. Each lane is one file in .factorial/processes/, named process-<name>.yaml. The part of the file name after process- is the name of the lane, and it must equal the name field in the file. The name has lowercase letters, digits and dashes. The name default is not allowed.

A project can have as many lanes as it needs. An issue picks its lane in one of two ways:

  • It carries a factorial:process:<key> label, for example factorial:process:hotfix.
  • It carries no such label, and the project's default process applies. An operator sets the default in the project configuration, not in the repository.

Factorial does not admit an issue that carries two process labels. It also refuses an issue that names a lane the repository does not offer. It never falls back to the default then. Only after Factorial selects the lane does it check the entry condition of that lane. If the condition is false, the issue waits.

Step kinds

kind: agent

Runs an agent on a workspace branch. Preconditions, postconditions, inputs, and outcome projections.

kind: human_gate

Waits for a PR approval. On approve it merges and advances. On reject it reruns a rework step with the review comments.

kind: action

Calls a named action from a closed registry, with no LLM.

kind: check

Waits until conditions hold on observed state, for example CI finishing. Only a timeout uses an attempt.

Gates

A human gate waits for a person to review a pull request. It names the pull request, and it can name who may decide:

- name: spec_review_gate
  kind: human_gate
  next: build
  gate:
    type: pr_approval
    pr: { head: "spec/{ticket}-*", base: "main" }
    onApprove:
      merge: true
    onReject:
      rerunStep: plan
      inputs: [pr_review_comments]

A reviewer can decide in two ways:

  • Approve. The gate merges the pull request and the ticket moves to the next step.
  • Request changes. The gate runs the rework step again, and the agent receives the review comments.

One approval from a qualifying reviewer is enough. An approval counts only on the commit that it approves. A commit that someone pushes after an approval needs a new approval. If GitHub refuses the merge, the gate keeps waiting and comments on the ticket with the reason. Tickets and gates shows how a reviewer decides.

How a ticket ends

A process ends at its terminal step. This step is virtual: it does not appear in the list of steps. The terminal block can close the ticket and post a comment.

terminal:
  step: done
  actions:
    closeTicket: true
    comment: "Complete. The change is merged to main."

If the block names no label, the ticket gets factorial:done.

A ticket can also leave the process early. This is an exit. Someone adds the factorial:exit label or uses the dashboard. Factorial cancels the live work and parks the ticket. To bring the ticket back, remove the label with the issue open. The ticket then gets a fresh attempt at the step it left.

Where the config lives

.factorial/                 # on the protected factorial branch
  processes/
    process-simple.yaml     # kind: Process
  agents/
    plan-agent/agent.yaml   # kind: Agent + prompt .md
    build-agent/
    test-agent/
  mcp/
    product-docs.yaml       # kind: McpServer
  skills/