label-driven autonomy

Issues in. Merged PRs out.

A label-driven autonomous ticket pipeline for any GitHub repository.

skill
npx skills add mikeroysoft/factory
CLI
uv tool install git+https://github.com/mikeroySoft/factory          # stable
uv tool install git+https://github.com/mikeroySoft/factory@main     # latest
uv tool install git+https://github.com/mikeroySoft/factory@v0.3.0   # pinned
factory dispatch PASS
$ factory dispatch

claimed       ready-for-agent → agent/<n>
attempt       worker → factory gate
gate          PASS
pr-opened     agent/<n>
review        APPROVE
merged        main

pipeline

State lives where the work does.

GitHub labels, comments, and PRs plus a gitignored .factory/ directory. The dispatcher is stateless and safe to re-run.

needs-triage
factory triage
needs-info
ready-for-agent
factory dispatch
ready-for-human
agent/<n> PR
factory-approved
merge stage
main

control surfaces

Deterministic where it matters.

Runs on your machine, on a systemd timer, with the agent CLIs you already have. No SaaS, no runner fleet.

Isolation

Worktree per ticket

Main is never touched by an agent.

Gate

Your gate, not ours

Any [[gate.check]] commands, exclusive lock for GPU or serial checks, and per-check timeouts.

Review

Second-model review

Path:line citations and a hard VERDICT.

Merge

Four-way condition

Gate, reviewer, GitHub CI, and freshness. Fail-closed.

Handoff

Evidence-bound escalation

ready-for-human with the reason, log path, and handoff note.

State

Stateless dispatcher

State is GitHub plus a gitignored .factory/ directory. Safe to re-run.

how a ticket moves

Seven stages. Evidence at every handoff.

Issues labelled ready-for-agent move through a worktree, your deterministic checks, a second model, GitHub CI, and freshness against main.

  1. 01

    Triage

    Deterministic lint rejects bodies under 80 chars or without acceptance criteria; the model then picks one of four labels. wontfix is only ever proposed.

  2. 02

    Claim

    The dispatcher re-reads the issue, assigns itself, takes a per-ticket flock, and creates .factory/wt-<n> on agent/<n>.

  3. 03

    Work

    The worker gets the issue, comments, standing instructions, and on retries the previous gate report or reviewer findings.

  4. 04

    Gate

    Conflict markers, your [[gate.check]] list in order, then a leak-scan of added lines. Exclusive checks serialise on a host-wide lock.

  5. 05

    Review

    The reviewer sees the diff, issue, and gate report. REVISE returns to the worker; APPROVE adds factory-approved.

  6. 06

    Merge stage

    One PR per pass, requiring gate PASS, factory-approved, green GitHub checks, and a head containing the current main tip.

  7. 07

    Escalation

    Unresolved work gets ready-for-human, loses its assignee, and gets a comment with the reason and worker log path. The worktree stays for forensics.

quick start

Bring your own checks. Keep your own machine.

Linux, Python >= 3.11, git, and gh. No Python dependencies.

shellquick start
cd your-repo
factory init            # .factory.toml, .gitignore, issue template, labels
$EDITOR .factory.toml   # put your real test/lint commands in [[gate.check]]
factory doctor          # tools, auth, remotes, model endpoint
factory install --dashboard   # systemd user timer every 10 min + dashboard on :8765
factory triage

Label needs-triage issues via the local model.

factory dispatch

One stateless pass: sync → merge stage → claim → work → gate → PR → review.

factory gate

Run the deterministic gate in the current worktree and write a Markdown report.

factory stats

Ticket table: attempts, review rounds, hours to merge; --json.

factory learn

Distill lessons from finished tickets into .factory-lessons.md.

factory dashboard

Local ops UI.

factory doctor / init / install

Onboarding.

configuration

Agent CLIs you already have.

Bring your own reviewer model and, optionally, a local OpenAI-compatible model for triage.

.factory.tomlconfig sample
[workers]                        # ticket label -> argv
default = ["omp", "-p", "--cwd", "{cwd}", "@{prompt}"]
chore   = ["droid", "exec", "-f", "{prompt}", "--auto", "medium", "--cwd", "{cwd}"]

[review]
command = ["codex", "exec", "{prompt}"]

[[gate.check]]
name = "tests"
run = ["cargo", "test", "--workspace"]
exclusive = true

[triage]
url = "http://127.0.0.1:11434/v1/chat/completions"
model = "qwen3:30b"