detent dispatches Claude Code at a queue of tasks, one at a time. It holds each one behind the repository's own gauge on the way out, has a second model read every green diff, merges what survives into a branch of its own, and writes down what each run cost in attempts, time and money.
npm i -g @kazimvakhobov/detent
Version 0.9.0 · Node 22 · macOS or Linux · Claude Code installed and signed in · zero dependencies
A detent is the catch that holds a mechanism in a defined position until it is deliberately released. Here it is released by a green gauge, and by nothing else.
The constraints are in the code, not in the prompt.
When the project's checks pass, not when it says it is done. Red means it does not get to leave, and it is handed the failure text to work from.
Every run is a line in a journal: which model, how many rounds, which stage it died on, how long, how much. "Which model should run this repository" becomes a number.
A second model reads the diff against the acceptance criteria. It may demote, never approve. Green is granted by machine checks only.
Two tasks off the queue. The lines are the ones detent run actually prints —
one as a task starts, one when it has stopped being detent's problem.
$ detent run
0042 acme-api wallet-endpoint …
tail -f runs/logs/0042-attempt-1.jsonl (one per attempt, up to -attempt-3)
0042 passed — merged into agent/dev (9f2c1ab), 2 attempts, $0.63
0043 acme-web tracking-map …
tail -f runs/logs/0043-attempt-1.jsonl (one per attempt, up to -attempt-3)
0043 paused (escalated): three identical failures on test — 3 attempts, $1.21
passed is the gauge's word and not the agent's: the commit named is a merge that
has already happened, and 2 attempts is the round a red gauge cost. 0043 spent its three and is
now in tasks/hold/ with its branch kept and a person told — the run did not quietly settle for it.
A task is a markdown file, and only its body reaches the agent. Up to attempts
rounds (three by default), each one spent the same way whether the gauge was red or the reviewer had a doubt.
Every run leaves one line in runs/journal.jsonl: outcome, rounds, the stage it died on,
the model that actually ran, and what it cost. The dashboard and detent summary are computed from
that file and nothing else.
detent reads and writes refs under refs/heads/agent/ and nothing else. Both gates
between the two territories are human operations, in both directions.
It refuses to start on a dirty tree, and never moves a ref outside its own namespace. Nothing in a
repository of your product is ever pushed by it, not agent/dev and not a tag.
detent dashboard serves one page on 127.0.0.1 that loads nothing from anywhere else.
The demo is that same page, built from this version's code against a made-up product called
acme: two repositories, a queue in all four folders, a journal, and a task that is running right now.
The frontmatter is bookkeeping. The spec written before the work is what the agent gets.
---
id: 0001
repo: acme-api
touches_contract: false
---
# Goal
One paragraph: what must work when this is done.
# Acceptance criteria
- [ ] a checkable statement
# Out of scope
What not to do while you are in there.
detent doctor | whether this product is in the shape detent expects |
detent init | scan the workspace and write product.json |
detent plan | discuss a task with an agent; it writes the task files |
detent run | work the queue |
detent promote | merge agent/dev into agent/staging behind the batch gauge |
detent summary | pass rate, rounds, cost per pass |
detent dashboard | a page that watches this product |
detent gauge | run a repository's gauge by hand |
detent recover | return repositories an interrupted run left behind |
detent prune | delete agent/task-* branches already merged |