@makingstuffs/devloop
v0.1.0
Published
A scripted agentic dev loop: intake -> triage -> dev agent -> checks -> independent review -> PR
Downloads
14
Maintainers
Readme
devloop
A scripted agentic dev loop. devloop takes a task from a one-line description
to an open pull request by running a fixed, auditable control flow:
task → intake → triage → [ dev agent → checks → independent review ]* → human gate → ship (push + PR)Install
# Install globally:
npm install -g @makingstuffs/devloop
# One-off usage without installing:
npx @makingstuffs/devloop <command>The command is devloop.
What devloop is (workflow, not agent)
devloop is not an autonomous agent that decides its own next step. It is a deterministic workflow — plain TypeScript control flow — that calls an LLM in exactly four judgment slots and nowhere else:
- Intake — turn a free-text task (or a spec file) into a structured
TaskSpec. - Triage — turn the spec into a dev brief, an independent review rubric, and an autonomy recommendation.
- Dev — the coding executor (headless Claude Code) makes the actual changes.
- Review — an independent reviewer judges the diff against the rubric.
Every decision that controls the loop — whether checks passed, whether the review verdict is a pass, whether to iterate again, whether to escalate, whether a guardrail forces a human gate, what to commit, when to push — is made by code from structured inputs. An LLM never decides continuation, and never has its output concatenated into a shell command.
Quick start
# 1. Install the prerequisites (see Auth model below):
# - Claude Code CLI, authenticated (`claude`)
# - GitHub CLI, authenticated (`gh auth login`)
# - No API key needed by default — judgment slots use your Claude Code login.
# (A provider API key is only needed if you point a slot at anthropic:/openai:/google:.)
# 2. In the target repo, write a default config and gitignore .devloop/:
devloop init
# 3. Run a task end-to-end (interactive intake, then autonomous where safe):
devloop run "Add a /health endpoint that returns 200 with a test"
# Or from a written spec, forcing a human gate before shipping:
devloop run "ignored when --spec is given" --spec ./task.md --gate
# Or exercise the whole pipeline with no API keys, binaries, or network:
devloop run "Try the pipeline" --dry-runAuth model
By default devloop needs no API key at all. Both the dev executor and the judgment slots run through your local Claude Code login:
- The dev executor authenticates itself. The coding agent is the headless
Claude Code CLI, which uses its own login (
claude— subscription or API, whatever you have configured). devloop does not pass it a key. - The judgment slots default to
claude-cli:*. Intake, triage, and review (and the commit-message call) default to model strings likeclaude-cli:claude-opus-4-8, which runclaude -pthrough the same Claude Code login — subscription auth, no API key. As a bonus, the CLI reportstotal_cost_usd, so these judgment calls are priced in real dollars in the run totals. - Provider API keys are only needed if you swap a slot to the AI SDK. Point a
slot at
anthropic:/openai:/google:(a one-line config change) and that provider's key must be present in the environment:ANTHROPIC_API_KEYforanthropic:*OPENAI_API_KEYforopenai:*GOOGLE_GENERATIVE_AI_API_KEYforgoogle:*
Note — do not pass
--bareto the CLI. devloop never does, and neither should you when reasoning about it:--bareskips the keychain read that subscription auth depends on, so aclaude-cli:*call would fail with "Not logged in · Please run /login". The judgment calls also run with--setting-sources ""and--tools ""so they load none of the target repo's settings or tools — a judgment call is pure generation and stays independent of the code it is judging.
--dry-run needs none of the above: no keys, no claude/gh binaries, and
no network.
Flags
devloop run <task> [options]
| Flag | Meaning |
| ---- | ------- |
| --spec <file> | Structure a pre-written spec document instead of interactive intake. |
| --gate | Force a human gate before shipping (tighten-only; see below). |
| --auto | Ratify an autonomous ship. Can only ever confirm an already-auto plan — it can never loosen a gate. |
| --model <id> | Override the dev executor model id for this run. |
| --config <path> | Use a specific config file (default: devloop.config.json in the cwd). |
| --dry-run | Exercise the full pipeline with no API keys, binaries, or network. Makes a real branch, commit, diff, and run directory, but stubs the LLM/dev/checks and never pushes or opens a PR. |
devloop init writes a default devloop.config.json and adds .devloop/ to the
target repo's .gitignore.
Config reference
devloop init writes a commented devloop.config.json. Every field has a default,
so a {} config is valid.
baseBranch(default"main") — the branch work targets and diffs against.branchPrefix(default"agent/") — prefix for generated work branches (<prefix><kebab-slug>-<shortid>).models— the model for each slot:intake,triage,revieware provider-agnostic"provider:modelId"strings. They default toclaude-cli:*(e.g."claude-cli:claude-opus-4-8"), which runs the judgment call through your local Claude Code login — subscription auth, no API key — and prices it in real dollars. You can also point them at the AI SDK:"anthropic:claude-opus-4-8","openai:gpt-5","google:gemini-2.5-pro"(those need the provider's env key).devDefault/devEscalationare raw Claude Code model ids for the executor. On the first failed review, the loop escalates the dev model fromdevDefaulttodevEscalationexactly once.- Swapping is a one-line change per slot, e.g. move review from the
subscription CLI to OpenAI by editing one string:
(When you swap to"review": "openai:gpt-5" // was "claude-cli:claude-opus-4-8"anthropic:/openai:/google:, ensure the matching provider key is in your environment.)
checks— an ordered list of{ name, cmd }deterministic checks run after each dev iteration (e.g. typecheck, lint, test). Any failure blocks the review.guardrails—protectedPaths(globs),maxDiffLines,maxDiffChars,dependencyChangesRequireGate. See Guardrails philosophy.loop—maxIterations,devTimeoutMinutes,budgetUsd(the loop escalates before starting an iteration that would exceed the budget).claude— headless executor settings:allowedTools,permissionMode, andmaxBudgetUsd. Note: the installed Claude Code CLI has no--max-turns;maxBudgetUsdis the per-dev-run spend cap that replaces turn caps.
Guardrails philosophy (tighten-only)
Autonomy can only ever be tightened, never loosened. Triage may recommend
auto, but any of the following forces a human_gate, and such gating is
sticky for the rest of the run:
- a changed file matches a
protectedPathsglob; - the diff exceeds
maxDiffLinesormaxDiffChars; - a dependency manifest/lockfile changed (when
dependencyChangesRequireGate); - you passed
--gate.
Nothing — not --auto, not a triage recommendation — can turn a human_gate back
into auto. --auto only ratifies a plan that was already auto. An oversized
diff (over maxDiffChars) escalates outright, because the reviewer must never
silently truncate the evidence it judges.
Run artifacts
Every run writes to .devloop/runs/<timestamp>-<slug>/ in the target repo
(gitignored by devloop init):
.devloop/runs/2026-07-07T10-30-00-add-a-health-endpoint/
spec.json # the TaskSpec produced by intake
plan.json # the TriagePlan (brief, rubric, autonomy, risks)
events.jsonl # one JSON line per state transition (audit trail)
result.json # terminal status: shipped | escalated | gate_declined | ship_failed | dry-run-shipped
HANDOFF.md # written on escalation or a declined gate — what a human must do
iteration-1/
dev-result.json # the executor's structured result (incl. session id)
checks.txt # each check's status, command, and output
verdict.json # the independent review verdict
feedback.md # the deterministic feedback fed to the next iteration
iteration-2/ ...The dev executor's session id is recorded in each dev-result.json; the same
id appearing in a later iteration is the proof that the loop resumed the prior
session rather than starting cold.
Exit codes
| Code | Meaning |
| ---- | ------- |
| 0 | Shipped (PR opened), or --dry-run completed. |
| 1 | Hard error (bad config, missing binary, dirty tree, cancelled intake, push/PR failure). |
| 2 | Escalated to a human (loop gave up, or the human gate was declined). The branch and run artifacts are preserved. |
Cost semantics
The dev-run cost is summed in USD by the loop (the executor reports
total_cost_usd per run) and printed on every terminal path after the loop —
shipped, escalated, gate-declined, or ship-failed.
Judgment calls are priced too when they run via claude-cli:* (the default).
The CLI reports total_cost_usd, so intake/triage/review and the commit-message
call become real dollars: intake + triage spend counts against the loop budget,
and review + commit-message spend is added to the final total. The cost line then
shows the grand total with a breakdown:
Total cost: $1.77 (dev $1.42 + judgment $0.35)result.json carries both the grand total (costUsd) and the judgment portion
(judgmentCostUsd).
If you swap a slot to an AI SDK provider (anthropic:/openai:/google:),
that call reports token usage but no price — it is logged to events.jsonl as
a judgment_usage event and the historical unpriced caveat is kept for it:
Total dev cost: $1.42 (judgment-call token usage is recorded, unpriced, in events.jsonl)⚠️ Security warning — the dev agent runs real shell in your repo
If Bash is in claude.allowedTools and you do not require per-command
approval, the dev agent can execute arbitrary shell commands in the target
repository. That is the price of unattended operation: the loop hands the
executor a task and lets it work without a human confirming each command. A
malicious or merely mistaken instruction can therefore delete files, exfiltrate
secrets, or run anything your shell can run.
For your first trials, run devloop inside a container, a VM, or on a throwaway
clone of a repo that has nothing to lose — never point it at a repo with secrets
or production credentials until you trust your configuration. If you want tighter
control, remove Bash from allowedTools (the agent keeps Read/Edit/Write) or
use a permissionMode that requires approval.
Everything devloop itself runs is built as an argument array — no LLM output is
ever interpolated into a shell string. The only place a shell string is
executed is your own checks commands, which come from your trusted config. The
warning above is specifically about what the dev executor may do with the
tools you grant it.
Contributing / Development
Clone the repo and install dependencies:
git clone https://github.com/makingstuffs/devloop.git
cd devloop
npm installRun the test suite and type-checker:
npm test # vitest run (160 tests)
npm run typecheck # tsc --noEmitBuild compiled output:
npm run build # tsc -p tsconfig.build.json → dist/Run from source (no build step — uses tsx directly):
npm run dev -- init
npm run dev -- run --dry-run "test task"
npm run dev -- --helpThe bin/devloop.mjs shim registers tsx and imports src/cli.ts directly,
so there is no build step for contributors during day-to-day development.
