tetra-run
v0.4.0
Published
A configurable, gated, cross-reviewed multi-agent code pipeline: write, gate, review, fix, stop.
Maintainers
Readme
tetra
A configurable, gated, cross-reviewed multi-agent code pipeline.
tetra stops at the human boundary — it never commits, pushes, or opens PRs — and the agent that writes the change is never the agent that judges it, while a real test gate must pass before anything is called done. It is the opposite of "point an agent at a repo and let a loop run unsupervised": every run ends with a clean-or-clearly-stopped working tree left for you to ship.
tetra runs a disciplined loop over any repository:
[plan → plan-review] → write → gate → review → (fix loop) → STOP- plan / plan-review (optional) — an agent drafts an implementation plan and an independent agent critiques it; the approved plan is handed to the writer. See Plan stage.
- write — an agent implements the task in the working tree.
- gate — the project's own tests/lint run. Nothing proceeds past a red gate.
- review — an independent agent (ideally a different model) reviews the diff.
- fix loop — if the gate fails or the reviewer requests changes, the feedback is handed back to the agent and the loop repeats (up to a configurable limit).
- STOP —
tetraleaves a clean working tree for you. It never commits, pushes, or opens PRs. That step is always yours.
The idea: keep the opinion (gate-before-merge, cross-model independent review, a bounded auto-fix loop) but never force a particular toolchain. Every stage's agent is swappable.
Why
Most agent tools let one model write and judge its own work. tetra separates the writer from the reviewer, insists the project's real tests pass before anything is considered done, and stops at the human-owned line (commit/push/PR).
Terminal states
Every run ends in exactly one of these. The working tree is always left for you — tetra never reverts your changes, and never commits them either.
| Outcome | Exit code | Working tree |
|---|---|---|
| Clean — gate passed and reviewer said APPROVE | 0 | left for you to commit |
| Gate still red after max fix iterations | 1 | left, gate still failing |
| Reviewer still REQUEST-CHANGES after max fix iterations | 1 | left, changes outstanding |
| Review produced no verdict (fail-closed) | 1 | left, not declared clean |
| A stage crashed (nonzero exit) | 1 | left at the point of failure |
| A stage timed out | 1 (inner 124) | left, the agent was killed |
A non-dry run always writes a run report recording which of these it hit.
Scope & honesty
tetra catches noisy failures: a red gate, a reviewer that requests changes, a crash, a hang. Those it stops on, loudly.
It does not catch silent ones. Plausible-but-wrong code that passes your gate and convinces the reviewer still reaches a clean-looking tree with exit 0. tetra is only as strong as your gate — a weak test suite is a weak gate. An independent reviewer reduces this risk but does not eliminate it. A clean run means "passed your tests and a second agent," not "is correct." The final judgement, like the commit, is yours.
Install
npm install -g tetra-runRequires Node.js ≥ 20, plus whatever CLIs your config references (e.g. claude, codex) installed and authenticated.
Usage
tetra run "Fix the off-by-one in split() and add a test" --repo . --base main
tetra run "..." --dry-run # print the plan without executing anything
tetra run "..." --plan # add a plan + plan-review round for this run
tetra run "..." --no-plan # skip plan stages for this run
tetra run "..." --report run.md # also write the run report to run.md--plan is the easy path: it injects default plan + plan-review stages at
the front of the pipeline (planner = your write agent, critic = your review agent)
without touching your config. If the write agent is agy (it can't plan — it
suppresses stdout off-TTY), the review agent plans instead (and if there is no stdout-capable agent to fall back to, --plan refuses with a clear error rather than injecting a broken plan loop). --no-plan strips any
plan stages for a single run. --plan and --no-plan can't be combined, and
--plan is a no-op if the pipeline already defines plan stages. For precise
control (different planner skills, a dedicated planning agent, custom
maxPlanIterations), declare the plan stages explicitly in config
instead.
Configure
Copy tetra.config.example.json to tetra.config.json in your repo. Agents are named once and referenced by stages, so you can assign any model to any role:
{
"agents": {
"claude": { "command": "claude -p {{PROMPT_FILE}}" },
"codex": { "command": "codex exec {{PROMPT_FILE}}" }
},
"pipeline": [
{ "stage": "write", "use": "claude" },
{ "stage": "gate", "command": "npm test" },
{ "stage": "review", "use": "codex", "failPattern": "REQUEST.?CHANGES" },
{ "stage": "fix", "use": "claude" }
]
}Want the writer and reviewer the other way round? Swap the use values. The gate is just a shell command — npm test, pytest -q, cargo test, anything.
Skills (per-stage rubrics)
Each stage can fire skills — vetted markdown rubrics that get injected into
that stage's prompt. tetra ships two first-party skill packs (code-review,
testing-strategy) and you can register your own:
{
"skills": {
"code-review": { "path": "./skills/my-code-review.md" }
},
"pipeline": [
{ "stage": "write", "use": "agy", "skills": ["testing-strategy"] },
{ "stage": "review", "use": "codex", "skills": ["code-review"] }
]
}So the writer always sees your testing rubric, the reviewer always applies your review rubric, etc. tetra bundles its own skills and injects them — it does not reach into or auto-install another tool's private skill system, and it never bulk-installs third-party skills.
Plan stage (optional)
Add a plan (and optionally plan-review) stage at the start of the pipeline
to think before writing:
{
"maxPlanIterations": 2,
"pipeline": [
{ "stage": "plan", "use": "claude", "skills": ["testing-strategy"] },
{ "stage": "plan-review", "use": "codex", "skills": ["code-review"] },
{ "stage": "write", "use": "agy" },
{ "stage": "gate", "command": "node test.js" },
{ "stage": "review", "use": "codex" },
{ "stage": "fix", "use": "agy" }
]
}- plan — the agent outputs an implementation plan as markdown (it edits nothing).
- plan-review — an independent agent critiques the plan and ends with a single
APPROVE/REQUEST-CHANGESline (same verdict detection as the code review). - On
REQUEST-CHANGES, the planner revises with the critique in hand, bounded bymaxPlanIterations(default2, must be>= 0). If it's still not approved when iterations run out, tetra proceeds to write with the last plan plus a warning — it never aborts, because the hard gate still protects you. - A planner or critic that crashes (nonzero exit) aborts the whole run — a failed agent is never silently treated as approval.
- The approved plan is injected into the write and fix prompts as a
## Plansection.
Both stages are entirely optional: a pipeline without them behaves exactly as before.
Agent choice:
planandplan-reviewconsume the agent's stdout, soagycannot serve those roles (it suppresses stdout when not attached to a TTY, like the reviewer role). Useclaudeorcodexfor plan/plan-review;agyis fine for write/fix.
Command tokens
| Token | Expands to |
|---|---|
| {{TASK}} | the task string you passed |
| {{PROMPT_FILE}} | temp file with the full prompt for the agent |
| {{DIFF_FILE}} | temp file with the working-tree diff vs baseBranch |
| {{BASE}} | the base git ref |
Config keys
Beyond agents / skills / pipeline, the top-level keys are:
| Key | Default | Meaning |
|---|---|---|
| baseBranch | "main" | Base git ref the review diff is taken against. |
| maxFixIterations | 3 | Max write→gate→review→fix rounds (must be >= 0). |
| maxPlanIterations | 2 | Plan-revision rounds for the optional plan pre-stage (>= 0). |
| reviewFailClosed | true | If the review stage exits cleanly but emits no verdict, abort rather than call the tree clean. Set false for the old warn-and-pass behavior. |
| defaultTimeoutMs | 600000 | Kill any stage running longer than this (exit 124). 0 = no timeout. |
A stage can override the timeout with its own timeoutMs (also 0 = off, >= 0):
{ "stage": "gate", "command": "npm test", "timeoutMs": 120000 }Timeout kill is best-effort (both platforms): tetra kills the whole process tree of a timed-out stage — on POSIX the child runs in its own process group (
detached) and is signalled viaSIGTERM, thenSIGKILLafter a 3s grace; on Windows it shells out totaskkill /T /F. This is best-effort: a process that re-parents or detaches itself can still outlive the timeout. Either way the stage resolves as124and the run stops.
Run report
Every non-dry run writes a markdown report to its temp workdir
(tetra-report.md) and prints the absolute path. Pass --report <path> to also
write a persistent copy. The report records the pipeline shape, a per-stage table
(exit code, verdict, duration, ok), the fix iterations used, the terminal
summary, and the final exit code — plus a footer restating
what a clean run does and does not guarantee.
The report contains only stage metadata, never the agents' stdout/stderr, so it won't leak anything they printed, and it is never written into the target repo by default (it can't dirty your tree or be committed by accident).
Treat it like a log. The report does echo your task text verbatim, and the default copy lands in a temp dir. If you point
--reportsomewhere, choose a path you won't commit (and don't put secrets in the task string).
Safety
- Never auto-commits / pushes / opens PRs —
requireHumanForPushis a hard, always-on boundary. - Bounded loop —
maxFixIterationsprevents infinite retries. - Treat repositories and their scripts as untrusted; review what your configured agents are allowed to run.
Status
v0.4.0. The core loop (write → gate → review → fix → stop), configurable agents, an optional plan / plan-review pre-stage, per-run --plan/--no-plan control, fail-closed review, per-stage timeouts, per-run reports, and a self-gating test suite are in place. Planned next: richer skill packs. See CHANGELOG.md.
License
MIT
