softwareaifactory.sh
selftest: 20 passed, 0 failed MIT licensed break/fix proven opencode · Claude Code · Codex

Your AI agents follow rules.
Ours exit non-zero if they don't.

Gates that block instead of ask. Roles that can't grade their own work. A commit lint that bans the word "verified" unless a command and its output back it up. We pulled this out of the factory we build our own product with — it's what stuck after an agent told us a change was verified when the check had never run.

Budget controls, run observability, and bounded repair loops — choose how much work your agents can take on.

For a solo dev driving one coding agent or a team whose agents open PRs on opencode, Claude Code, or Codex
watch a gate fire bash

# Actual behavior of scripts/hooks/test-edit-denial.sh and scripts/selftest/run.sh — not a mock.

$ budgeting · observability · loop engineering

Give your agents limits. See where the work goes.

Choose a local preview, a single budgeted invocation, or a bounded repair loop. The shared controls support Codex, Claude Code, and OpenCode through their native CLIs. Model execution is opt-in; your existing permissions and factory gates still apply.

Available on main. These additions are not included in the current v0.1.6 release. Use the install or upgrade instructions with --ref main to try them.

factory budget

Budgeting: set limits before launch

Limit task attempts, session runs, execution time, and concurrent factory invocations. Preview the plan locally before enabling a run. Optional estimated-cost thresholds can warn or stop further launches; they are not a guaranteed dollar cap. Choose your budget controls →

factory budget report

Observability: inspect each attempt

See the harness, role, requested model, duration, outcome, reported tokens, and available cost estimates. Missing usage stays unknown. The local, gitignored ledger stores metadata without prompts or responses; export reports as JSON. Native harness history follows its own settings. Explore run reporting →

factory loop

Loop engineering: repair within bounds

Manual mode runs your configured check without calling a model. Explicit bounded mode coordinates implementation, checks, separate review, and repair through the same budget. It stops on exhausted limits or stalled progress and leaves evidence and a next action for handoff. Configure a bounded loop →

# inspect limits; new runs are disabled until you opt in $ ./factory budget plan --harness codex --session demo --task first # inspect local run history $ ./factory budget report --session demo --json # preview the default manual loop; no checks or model calls $ ./factory loop plan --harness codex --session demo --task first

Use --harness claude or --harness opencode with the same commands. Budget limits cover factory-managed launches, not unrelated interactive sessions or individual model turns inside a harness. Bounded loops require both loop_enabled and budget_enabled; allow for reviewer calls in the budget.

$ prompt · loop · harness · graph

Everyone's drawing the graph. You're already standing on it.

The words everyone's chasing are a ladder. A prompt is a sentence. A loop is a cycle. A harness is the floor the agent stands on. A graph is the shape of the work — nodes that think, edges that carry results. A factory is the floor and the graph. Not the influencer version — the one you can run and enforce.

Graph conceptWhat it already is here
Node — a bounded job, one in, one outA role — spec-writer, implementer, reviewer — defined once, generated for every harness
Edge — a verified hand-off, not just "and then"A gate — the hook that checks what crosses, and exits non-zero if it's wrong
Verifier — a skeptic before a finding countsThe reviewer — adversarial, and never the same model that wrote the code
Tier the models per nodeThe economy cost profile — cheap model for mechanical roles, frontier for judgment

You don't become a "graph architect." The graph is the floor: roles are the nodes, gates are the edges, the reviewer is the skeptic, models tier by role. And because the roles are canonical — opencode is the source, Claude Code and Codex are generated — the same graph runs on all three. Why it maps this way →

$ [1/4] why this exists

Prose rules decay. Exit codes don't.

Across four review rounds of real agent-written code, one pattern held:

4

consecutive review rounds before the pattern was undeniable

100%

of false "verified" claims were checks that had never been run

0

checks that actually ran and lied

a rule that asks
# INSTRUCTIONS.md
- never edit test files
- always verify before claiming done

compliance: whatever the model
remembers today
a rule that blocks
$ ./scripts/hooks/test-edit-denial.sh
DENIED: implementer role cannot
edit test files.
exit code: 2

compliance: not optional

The underlying idea — computational controls beat inferential ones — comes from Birgitte Böckeler at Thoughtworks. We kept applying it until nothing important was left as prose. Read the full Verification Contract →

break FAIL revert PASS

A check you've only seen passing proves nothing. Every gate ships with this proof in CI — and the installer runs it on your machine before it will say "done."

To be clear: this is how we build, not how you should. There are other serious takes on agent reliability worth your time. This is just the one we can vouch for, since we live in it daily.

$ [2/4] how this approach differs

Six places we chose differently.

The left column is the common default across today's agent tooling — instruction files, trusted-because-green checks, one session doing everything. It exists for good reasons. The right is what survived our failures.

·Rules live in prompts. Compliance is inference.
$Rules are hooks in the write path. exit 2 is a fact.
·Checks are trusted because they pass.
$Every gate proven break → FAIL → revert → PASS, in CI.
·"Verified" means the agent said so.
$WROTE / RAN / OBSERVED — no evidence, no "verified." The commit lint enforces it.
·One session writes the code, the tests, and the approval.
$Spec-writer writes tests. The implementer can't touch them. The reviewer is a different model.
·One platform. The workflow is the lock-in.
$One canon generates adapters for opencode, Claude Code, and Codex. Models are swappable.
·"Self-improving" agents grade their own improvements.
$The ratchet: tightens automatically, loosens only by recorded human decision.
$ [3/4] what's inside

Small, inspectable, and honest about its own maturity.

scripts/hooks/

Deterministic gates

Test-edit denial, evidence-checking commit lint, decision gate, drift checks — all configured by one flat factory.yaml. Hooks stay byte-identical across adopters; upgrades are file copies. Hook reference →

.opencode/ · .claude/ · .codex/

Five agent roles

Defined once, generated per harness, separated by permissions the hooks enforce — not by politeness. One keeps a cited wiki of your codebase — Karpathy's LLM-wiki pattern, with the provenance enforced rather than hoped for.

scripts/selftest/

Break/fix proofs

The suite violates each rule on purpose and asserts the gate fires. factory-init won't claim success until it passes on your repo.

eval/

Agent evals that expire

Score agents on real tasks: a task is a red spec plus an oracle, and the pass rate is measured over N runs — a run only counts if the oracle passes untampered. Each baseline records what it was measured against, so when the oracle or instructions change it reports stale, not a false “no regression.” Eval reference →

workflows/

The graph, in plain text

A recipe is a graph you can read: nodes are roles, edges are code, findings pass a verifier before they count. workflow-lint enforces that shape — no agent spawned for plumbing an edge could do. One recipe, run natively by all three harnesses. Workflows →

factory report

Cost, without the vanity number

What the gates caught, enforced at zero model tokens, plus one clearly-labeled estimate. It refuses to print “tokens saved” — that compares this run to one that never happened. Cost & models →

factory metrics

The numbers that don’t flatter you

Whether the factory is real or installed theatre — gates armed versus inert, what they blocked, how often you route around the same one, how much work got reworked or reverted, and whether your commits cite evidence for what they claim. Most of it is read from git history, so it says something the day you install rather than after months of collecting events.

Enforcement is the factory real, or installed theatre? gates installed 12 config-armed gates 8 armed, 4 inert blocks caught 31 in window commit-message-lint 22 test-edit-denial 9 same gate 3+ times in an hour 4 hour(s) — friction Loop health is work converging, or circling? reworked 58 of 117 files — changed in more than one commit

Every metric names the decision it informs; a number nobody acts on is noise wearing a lab coat. The uncomfortable ones are first-class — inert gates, repeat blocks, rework, stale baselines — for the same reason there is no “tokens saved” headline. And a gate that could block without recording it is reported too, because otherwise a silent gate reads as a quiet one.

Local only. The numbers are computed on your machine, from your repository, for you. Nothing is transmitted — no exporters to configure, no server, no daemon, no account. --json exports a versioned document if you want it somewhere else; --html writes a self-contained page that fetches nothing when you open it. Metrics reference →

Language packBlessed stackMaturity
GoGinkgo + Gomega · golangci-lint · gosec · govulncheck · gremlinsbattle-tested
TypeScript / React / VueVitest · Biome (React/Vue-aware) · tsc · Stryker · OSV-Scannerexperimental
Java / Spring BootJUnit 5 + AssertJ + Testcontainers · Spotless · Error Prone · SpotBugs · OSV-Scanner · PITexperimental

battle-tested = a production service shipped under it. experimental = no proven adopter yet. Labels change on real adoption — not before. The core — commit gates, push gates, role separation, the verification contract — is language-agnostic and works with no pack at all. A pack only arms the test-file pattern and check command for your language, so Python, Rust, or Ruby projects get everything except those two.

Frameworks ride on the language pack. React and Vue use the TypeScript pack — Biome auto-applies their rules from your package.json — and Spring Boot uses the Java pack (JUnit 5 + Testcontainers). factory-init detects them. Polyglot? Install several at once: --pack go,typescript.

Each role runs on a cost-appropriate model. Reviewer and spec-writer get a frontier model; refactorer, wiki-maintainer, and background tasks get a cheaper one — routed per harness, so opencode, Claude, and Codex each use their own native model tiers. It's opt-in, and it relaxes no gate: the same hooks check the output whichever model wrote it, which is what lets a cheaper model run safely. Cost & models →

Configuration is one flat file — no nesting, no schema to learn:

# factory.yaml project_name: myproject protected_paths: "internal/billing scripts/hooks" test_file_patterns: "_test\.go$" check_command: "make check"
$ [4/4] install

One command in. Proof before "done."

Bare, the installer only fetches — a pinned copy into one folder, running nothing it downloaded. Add init and it also sets up the current repo. The word is your consent; the plain command never touches your project.

Needs git, a coding harness (opencode, Claude Code, or Codex), and your language's tools. factory-init runs prereq-check first and tells you what's missing.

# one shot: fetch, then set up the current repo (prompts you for a few values) $ cd your-project $ curl -fsSL https://softwareaifactory.sh/install.sh | sh -s -- init # arm a language pack in the same step (go is battle-tested): $ curl -fsSL https://softwareaifactory.sh/install.sh | sh -s -- init --pack go # polyglot? pass several — a Go backend with a TS frontend: $ curl -fsSL https://softwareaifactory.sh/install.sh | sh -s -- init --pack go,typescript # installs and upgrades are pinned to the latest release; --ref takes any branch or tag: $ curl -fsSL https://softwareaifactory.sh/install.sh | sh -s -- init --ref main $ curl -fsSL https://softwareaifactory.sh/install.sh | sh -s -- upgrade --ref main # or fetch only, run the setup yourself later: $ curl -fsSL https://softwareaifactory.sh/install.sh | sh $ ~/.software-factory-template/scripts/factory-init.sh # rather read before you run? so would we: $ curl -fsSLO https://softwareaifactory.sh/install.sh && less install.sh && sh install.sh
1

installer fetches a pinned copy — and executes none of it

2

factory-init asks a few questions, writes factory.yaml, installs the gates

3

init breaks a rule on purpose, watches the gate fire, and only then says "done"

After it says "done"

  1. make sync-harnesses — generate the Claude & Codex adapters from the canon
  2. ./factory doctor — see which gates are armed, inert, or stale, and watch each armed one fire its break/fix proof
  3. ./factory report — what the gates caught, enforced at 0 model tokens, plus one labeled cost estimate (never a vanity "tokens saved" number)
  4. Open your harness (opencode, Claude Code, or Codex) — the roles are already wired
  5. Point the spec-writer at a change; it writes red tests, the implementer makes them pass, a different-model reviewer checks it
  6. Install the push gate: git config core.hooksPath .githooks

Or just watch it fire — no install

# clone and run the break/fix suite; nothing touches your machine $ git clone https://github.com/anoop2811/software-factory-template $ cd software-factory-template && ./scripts/selftest/run.sh # 20 gates break, fire, and pass in front of you
Does it work with my existing CI?

The gates are plain shell scripts, so you can call them from any CI — or from the git pre-push hook, which needs no CI at all. One caveat: the ready-made workflow we ship is GitHub Actions. On GitLab CI, Jenkins, CircleCI, and the like, you invoke the same scripts from your own pipeline config — a handful of lines, but that part is yours to write.

Can I take just the commit lint, without the role separation?

Yes. Every gate is independent. Adopt them one at a time — the adoption guide covers going incremental in a brownfield repo.

How do I keep it up to date?

From inside the repo: curl -fsSL https://softwareaifactory.sh/install.sh | sh -s -- upgrade — it refreshes the template and applies the update to the repo you're in, exactly like init acts on the current directory (./factory upgrade does the same locally). It refreshes the hooks, scripts, and gates in place, never touches your factory.yaml, code, or customized files, and only reports which of those changed upstream. It upgrades this repo, not every repo on your machine — each owns its committed framework files. Then ./factory doctor confirms every gate still fires. Nothing is committed for you.

What if I don't use opencode?

The canonical config lives in opencode.json, but a sync script generates equivalent Claude Code and Codex configs. The enforcement layer is plain shell — it doesn't care which harness drives.

What about React, Vue, or Spring Boot?

Frameworks ride on the language pack — they don't get their own. React and Vue use the TypeScript pack, and Biome auto-applies its react/vue rules when it sees the framework in your package.json. Spring Boot uses the Java pack — its JUnit 5 + Testcontainers stack is Spring Boot's own blessed testing. factory-init detects each and points you at the right pack. A framework-specific rule beyond that is a small custom hook, not a new pack.

My language isn't Go, TypeScript, or Java.

The core works anyway. Commit gates, push gates, role separation, and the verification contract are language-agnostic. A pack only arms the test-file pattern and check command — set those two in factory.yaml by hand and you have the full thing.

Will it slow down my dev loop?

The gates are the fast local rail — a hook that exits in milliseconds. They run where you'd otherwise wait on a human reviewer to notice the same mistake. That trade goes the other way.

Clone it. Break a rule.
Watch the gate fire.

The fastest way to evaluate this template is to try to violate it. That is also the point.