{"id":51178327,"url":"https://github.com/rivet-dev/riveter","last_synced_at":"2026-06-27T05:30:26.219Z","repository":{"id":365816803,"uuid":"1248659715","full_name":"rivet-dev/riveter","owner":"rivet-dev","description":"Ralph the Riveter — an autonomous coding loop using jj and pluggable agents.","archived":false,"fork":false,"pushed_at":"2026-05-24T23:27:45.000Z","size":66,"stargazers_count":6,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-19T03:22:51.816Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rivet-dev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-05-24T23:27:22.000Z","updated_at":"2026-06-12T04:55:01.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/rivet-dev/riveter","commit_stats":null,"previous_names":["rivet-dev/riveter"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/rivet-dev/riveter","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rivet-dev%2Friveter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rivet-dev%2Friveter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rivet-dev%2Friveter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rivet-dev%2Friveter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rivet-dev","download_url":"https://codeload.github.com/rivet-dev/riveter/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rivet-dev%2Friveter/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34843146,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-27T02:00:06.362Z","response_time":126,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-27T05:30:23.969Z","updated_at":"2026-06-27T05:30:26.211Z","avatar_url":"https://github.com/rivet-dev.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Ralph the Riveter\n\nA focused reimplementation of the [Ralph](https://ghuntley.com/ralph/)\nautonomous coding loop in Rust. Slimmer than the original bash version, with\npluggable agents (`codex`, `claude`, or `mock`), per-iteration `jj` commits,\nand in-place execution (no branch switching).\n\nThe binary is named **`riveter`** (the tool); the methodology it implements is\n**Ralph** (one focused story per fresh agent context, persistent state on disk,\n`jj` as the review surface).\n\n---\n\n## Why Ralph the Riveter\n\n- **Fresh context per story.** Each iteration spawns a new agent process with\n  no memory of prior iterations. The agent's working set is exactly one user\n  story + the codebase + the progress log — never the full transcript of past\n  attempts. This keeps the context window small and focused, which is the\n  single biggest lever on agent quality and cost.\n- **Persistent state lives on disk, not in-context.** `prd.toml` tracks which\n  stories are done — the agent reads it (plus its assigned story) at the start\n  of every run and flips `passes = true` when the story's acceptance criteria\n  are met.\n- **`jj` is the review surface.** Because every iteration is one `jj` commit\n  on `@`, you review Riveter's work the same way you review your own:\n  `jj log`, `jj diff -r \u003cid\u003e`, `jj abandon \u003cid\u003e` to reject,\n  `jj squash`/`jj split` to reshape. No branch dance, no PR ceremony, no\n  merge conflicts with your in-flight work.\n- **Pluggable agents.** Codex and Claude are interchangeable — pick the model\n  that's good at the kind of work the spec needs. Same loop, same artifacts,\n  same review flow.\n\n---\n\n## Intentional deviations from Ralph\n\nRiveter follows the Ralph methodology faithfully, but a handful of design\nchoices were deliberately changed from the [original Ralph](https://github.com/snarktank/ralph)\nbash implementation:\n\n- **No `progress.txt` style early-termination signal.** Ralph greps the last\n  20 lines of agent output for `\u003cpromise\u003eCOMPLETE\u003c/promise\u003e` and exits the\n  loop on match. That's brittle: agents fail early, get rate-limited, or\n  print the sentinel as part of their own instruction echo, and the loop\n  exits prematurely. Riveter terminates only when re-reading `prd.toml`\n  shows every story `passes = true`. The agent flipping that flag is the\n  one authoritative signal.\n\n- **No feature branches.** Ralph creates/checks out a `branchName` from the\n  PRD on every iteration. In practice branches get in the way of stacking,\n  in-flight human edits, and any kind of human-in-the-loop review — every\n  iteration becomes a merge negotiation. Riveter works in-place on `@` and\n  never touches branches or bookmarks. You stay on whatever you were on,\n  and Riveter's commits land on top of your work like any other commit.\n\n- **`jj` instead of `git`.** `jj`'s inverted workflow (create a revision\n  before editing, not after) maps cleanly onto an autonomous loop: each\n  iteration is one `jj describe` + `jj new`, with no staging step to forget\n  and no \"did the agent commit?\" ambiguity. Review becomes\n  `jj log -r 'description(glob:\"[[]RIVETER*\")'`; rejection becomes\n  `riveter reject` (which amends, not abandons). `jj`'s lossless operation\n  log also means a broken iteration is never destructive — you can always\n  `jj op undo`.\n\n- **`prd.toml` instead of `prd.json`.** TOML allows comments, trailing\n  commas don't matter, and the format reads cleanly when humans inspect or\n  edit it mid-run. It's still trivial for the agent to parse and round-trip\n  via `toml_edit` without reformatting the human's hand-written sections.\n\n- **Array order, not a `priority` field.** Ralph stories carry an integer\n  `priority` and the agent picks \"highest priority pending\". In practice\n  the priority field is just a second representation of \"what order should\n  we do these in\" — confusing for both humans (which number means more\n  important, 1 or 10?) and agents (do priority ties break by id?). Riveter\n  stories are executed strictly in array order. To reorder, you reorder\n  the array. The `id` field is purely a stable reference for commit\n  messages and `riveter reject`.\n\n- **Quality-check / clean-fix / browser-test rules live in the prompt\n  template, not external `AGENTS.md` / `CLAUDE.md` files.** Ralph asks the\n  agent to read+update `AGENTS.md`/`CLAUDE.md` files in nearby directories.\n  Riveter inlines those rules directly into the rendered prompt every\n  iteration. **Trade-off:** the rules aren't automatically loaded when the\n  same agent works in your repo outside a Riveter run, and they aren't\n  shared across runs. **Win:** the rules are guaranteed to be in the\n  context window for every Riveter iteration, you don't have to maintain\n  parallel `AGENTS.md` copies, and you can tweak them once in the\n  Riveter template instead of per-project. Project-specific conventions\n  still flow through `learnings.md` (per-run) and your existing\n  `AGENTS.md` files (which the agent will read when it touches nearby\n  code), so you don't lose anything important.\n\n---\n\n## Quick start\n\n### 0. Install the bundled `riveter-create-run` skill (one-time)\n\n```sh\nriveter install-skill\n```\n\nStages the bundled skill at `~/.riveter/skill-staging/` and hands off to the\ninteractive [`npx skills add`](https://github.com/vercel-labs/skills) UI — pick\nyour agents, scope (project/global), and install mode there. Requires `npx`\non PATH.\n\n### 1. Generate a run folder\n\nInside any agent session (Codex, Claude, Amp, ...) that has access to the\n`riveter-create-run` skill installed above, feed it your spec:\n\n\u003e Riveter create run: add high/medium/low priority to tasks (default medium);\n\u003e show colored badges; filter by priority.\n\nThe skill writes `\u003cstate-dir\u003e/runs/\u003crunId\u003e/{prd.toml,spec.md}` and prints the\nrunId, e.g. `task-priority-a1b2c3d4`.\n\n### 2. (Optional) inspect / edit the PRD\n\n```sh\n$EDITOR ~/.riveter/runs/task-priority-a1b2c3d4/prd.toml\n```\n\n### 3. Run the loop in your jj repo\n\n```sh\ncd ~/projects/my-app                       # a jj-managed repo\nriveter run -r task-priority-a1b2c3d4 \\\n            -a codex -m gpt-5.5 -t high\n```\n\n`riveter` never switches branches/bookmarks. It commits each iteration on `@`\nand then opens a fresh empty commit on top. Resume by re-running the same\ncommand — passing stories are skipped.\n\nBy default Riveter **does not stream the agent's chatter** to your terminal.\nYou'll see iteration banners, `[agent]`, `[jj]`, `[prd]`, and `[reject]`\nprogress lines from Riveter itself, plus a `tail -f` hint per iteration so\nyou can watch the agent live if you want:\n\n```\n============================================================\n  iteration 1/10 (#1) · #1 \"Add priority field to tasks table\"\n============================================================\n[agent] codex (model=gpt-5.5, thinking=high)\n[agent] live output suppressed. To watch this step:\n        tail -f ~/.riveter/runs/task-priority-a1b2c3d4/iterations/001/stdout.log\n        tail -f ~/.riveter/runs/task-priority-a1b2c3d4/iterations/001/stderr.log\n```\n\nPass `--show-agent` if you want the live `│ ...`-prefixed tee in your\nterminal anyway. The full transcript is always written to disk regardless.\n\n---\n\n## Reviewing a run\n\n```sh\njj log -r 'description(glob:\"[[]RIVETER*\")'   # all Riveter commits\njj diff -r \u003cchange_id\u003e                        # one iteration\njj abandon \u003cchange_id\u003e                        # reject a bad iteration\n```\n\nCommit subjects encode the run id, story id, and model:\n\n```\n[RIVETER(task-priority-a1b2c3d4,#2,gpt-5.5)] chore: Display priority badge on task cards\n```\n\n## Rejecting bad iterations\n\nInstead of `jj abandon`-ing the bad commit (which throws away the agent's\nattempt entirely), Riveter has a first-class **reject** workflow that keeps\nthe rejected commit in history and surfaces it to the next iteration as\nprompt context so the agent can learn from what was tried:\n\n```sh\nriveter reject -r \u003crunId\u003e [-c \u003cchange_id\u003e] [-m \"reason for rejection\"]\n```\n\nWhat it does:\n\n1. Resolves the target jj change. If `-c` is omitted, it picks the most\n   recent `[RIVETER(\u003crunId\u003e,...)]` commit reachable from `@`.\n2. Amends that commit's description from\n   `[RIVETER(\u003crunId\u003e,#\u003cid\u003e,\u003cmodel\u003e)] chore: \u003ctitle\u003e` to\n   `[RIVETER-REJECTED(\u003crunId\u003e,#\u003cid\u003e,\u003cmodel\u003e)] chore: \u003ctitle\u003e | rejected: \u003creason\u003e`,\n   with a reviewer note appended to the body. The commit and its diff\n   stay in history.\n3. Flips that story's `passes` flag back to `false` in `prd.toml`.\n\nOn the next `riveter run -r \u003crunId\u003e`, the loop scans `jj log` for any\n`[RIVETER-REJECTED(\u003crunId\u003e,#\u003cstoryId\u003e,*)]` commits reachable from `@` and\nincludes each one's description + diff in the prompt under a \"Previously\nrejected attempts on this story\" section. The agent is instructed to read\nthem and avoid repeating the same mistakes.\n\nIf you want to throw an attempt away entirely instead of keeping it as\nlearning material, `jj abandon \u003cchange_id\u003e` + manually editing `prd.toml`\nstill works — but `riveter reject` is the recommended path.\n\n---\n\n## Tuning cost vs. quality\n\n| Flag         | Cheap \u0026 cheerful  | Default (codex)         | Default (claude)         |\n|--------------|-------------------|-------------------------|--------------------------|\n| `--thinking` | `low`             | `high`                  | `xhigh`                  |\n| `--model`    | mini tier         | `gpt-5.5`               | `claude-opus-4-7`        |\n\n`--model` and `--thinking` defaults are now **per-agent**: codex →\n`gpt-5.5` + `high`, claude → `claude-opus-4-7` + `xhigh`. Pass `-m` /\n`-t` to override either.\n\n### `--fast` (fast service tier)\n\n```sh\nriveter run -r \u003crunId\u003e --fast\n```\n\n`--fast` is **not** a different model — it's a service-tier knob. For\n**codex** it adds `-c service_tier=\"fast\"` to the underlying invocation,\nwhich per OpenAI's docs makes the same model generate tokens about 1.5×\nfaster for about 2.5× the cost. It does **not** change `--model` or\n`--thinking`; combine it with any model/effort you want. For **claude**\nthere's no equivalent today and `--fast` is a no-op (logged with a\none-line warning).\n\nDefaults are deliberately on the expensive end — Ralph relies on each\niteration being good enough that the loop doesn't waste turns. Dial down for\ncheaper exploration; dial up when stories keep failing acceptance.\n\n---\n\n## `--agent mock` (for hacking on Riveter)\n\nThe built-in `mock` agent has no LLM dependency. It walks the PRD\ndeterministically (touches one file per story, flips `passes = true`), making\nit perfect for integration tests and quick sanity checks:\n\n```sh\nriveter run -r \u003crunId\u003e --agent mock\n```\n\n---\n\n## Layout\n\n```\n~/.riveter/runs/\u003crunId\u003e/\n  ├── spec.md               # verbatim user input\n  ├── prd.toml              # source of truth for progress (incl. per-story `notes`)\n  ├── learnings.md          # append-only learnings log; agent reads + writes\n  └── iterations/\n      ├── 001/\n      │   ├── prompt.txt    # exact bytes sent to the agent\n      │   ├── stdout.log    # tee'd output\n      │   ├── stderr.log\n      │   └── exit.txt\n      ├── 002/\n      └── ...\n```\n\n`learnings.md` is created on the first iteration. Its `## Codebase Patterns`\nsection is the agent's authoritative summary of project conventions it has\nlearned across iterations — the prompt template instructs every fresh agent\nto read it first and append a new learnings block at the end.\n\nEach `[[stories]]` entry in `prd.toml` also has a `notes` field (free-form\nstring, defaults to empty) that the agent is allowed to overwrite with\nper-story scratch context — failing commands, design dilemmas, references —\nso a future iteration on the *same* story can pick up where the previous one\nleft off.\n\nRuns are **never auto-deleted**. Iteration dirs are append-only across\nre-runs (numbering continues `003`, `004`, …). To clean up:\n\n```sh\nls -lt ~/.riveter/runs/                  # oldest at the bottom\nrm -rf ~/.riveter/runs/\u003crunId\u003e           # one\nrm -rf ~/.riveter/runs/*                 # all\n```\n\nOverride the location with `RIVETER_STATE_DIR=/path/to/dir`.\n\n---\n\n## Building\n\n```sh\ncargo build --release\n./target/release/riveter --help\n```\n\nTests:\n\n```sh\ncargo test\n```\n\nThe default integration test uses `--agent mock` so no LLM credentials are\nrequired.\n\n### End-to-end test (real codex)\n\nThere is a separate, gated E2E test that exercises the **`riveter-create-run`\nskill + the loop with real `codex`** against a temp `jj` repo. It's\n`#[ignore]` by default because it costs money and takes ~90 s.\n\n```sh\nRIVETER_E2E=1 cargo test --test e2e_codex -- --ignored --nocapture\n```\n\nKnobs (env vars, all optional):\n\n| Var                      | Default   | What                                       |\n|--------------------------|-----------|--------------------------------------------|\n| `RIVETER_E2E`            | unset     | master switch; test SKIPs if unset         |\n| `RIVETER_E2E_MODEL`      | `gpt-5.5` | model passed to `codex` (must work in your codex auth) |\n| `RIVETER_E2E_THINKING`   | `low`     | reasoning effort                           |\n| `RIVETER_E2E_MAX_ITER`   | `3`       | hard cap on loop iterations                |\n| `RIVETER_E2E_TIMEOUT_S`  | `600`     | total wall-clock budget                    |\n\n---\n\n## Exit codes\n\n| Exit | Meaning                                                  |\n|------|----------------------------------------------------------|\n| 0    | All stories `passes = true`                              |\n| 10   | Agent exited non-zero                                    |\n| 12   | Agent exited 0 but `jj` working copy is clean            |\n| 13   | A `jj` subcommand failed                                 |\n| 14   | `prd.toml` missing/invalid after iteration               |\n| 20   | Hit `--max-iterations` with stories still pending        |\n| 30   | `riveter validate`: schema/layout errors                 |\n| 31   | `riveter validate`: run folder missing                   |\n| 32   | `riveter validate`: I/O error                            |\n| 40   | `riveter reject`: no matching `[RIVETER(...)]` commit    |\n| 41   | `riveter reject`: target commit has unparseable subject  |\n| 130  | SIGINT/SIGTERM                                           |\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frivet-dev%2Friveter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frivet-dev%2Friveter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frivet-dev%2Friveter/lists"}