A meta-harness for coding agents. Drive a fleet of agents through a spec-driven workflow across git worktrees — from one TUI, or headlessly from your own agents and CI.
The bottleneck in agentic coding isn't the agents anymore. It's you.
One coding agent is a pair programmer. Five are a management problem: a pile of terminals, worktrees you have to keep straight in your head, and an agent that has been silently blocked on a question for the last twenty minutes. Meanwhile every step burns frontier-model tokens whether it needs frontier intelligence or not, and nothing stops an agent from jumping straight to code without a spec or shipping unreviewed work.
gummi replaces the pile of terminals with one board. Every unit of work is a card moving through a fixed workflow; every card gets its own git worktree and branch; every stage is performed by an agent whose model you choose. gummi's job ends at a verified branch — land it with the built-in one-key squash-merge or however you like; PRs and releasing stay in your hands.
You are the scarce resource. gummi's parallelism is attention-based, not throughput-based. By default one autonomous agent runs at a time while the rest queue, and everything that needs a human — gates, agent questions, failures, exhausted budgets — lands in a single needs-attention inbox. The point isn't to run ten agents at once; it's to make context-switching between features cheap and to never leave an agent waiting on you without you knowing.
The process is fixed; only the spend is yours to choose. The workflow is compiled in, never configurable: no implementation without an approved spec, no merge without review and verification. The only degrees of freedom are skip flags for the early design stages and automatic rerun transitions (fix → re-review). Review and Verify can never be skipped. You can't accidentally configure the quality floor away, because there is no configuration.
Frontier models only where they earn it. Stages are performed by
roles (architect, implementer, reviewer, scribe), and a
profile maps roles to concrete models — a frontier model for design
and review, a cheap or local model for mechanical steps. Each feature
carries a credit envelope every stage draws from, with a human top-up
gate when it runs dry. Same process every time; spend chosen per
feature.
Two kinds of work item share the machinery:
feature FD-NNN todo → brainstorm → spec → plan → implement → review → verify → done
bug BG-NNN todo → triage → diagnose → fix ──────────────↗ (same quality floor)
- Design is a conversation. Brainstorm and Spec (Triage and Diagnose
for bugs) are interactive — you talk to the architect in gummi's chat
pane, and the durable artifact is a markdown spec that lives in the
repo's
.gummiworkspace. The spec — not the transcript — is the context carrier between stages, which keeps token windows small. - The quick route trades gates, never artifacts. For well-understood
work,
qon the creation form picks the quick route: brainstorm and plan are skipped, and the spec stage drafts the complete spec — implementation steps included — in one pass for you to steer and approve. Same spec, same review/verify tail; one conversation and one gate instead of three. If it outgrows quick,Prestores the plan stage — loosening a skip is always allowed, tightening one never is. - Plans get an adversarial read. Before a plan reaches your approval
gate, a fresh-context reviewer critiques it for security, correctness,
and completeness. Findings land as
%%threads in the spec; serious ones trigger an automatic replan, capped. - Implementation runs alone. Implement/Fix runs autonomously in the
feature's worktree, streaming activity to the card. Agents can ask you
bounded questions mid-turn via a built-in
ask_usertool — the question renders as an inline picker, the blocked turn spends no tokens while it waits, and answers anchored to a spec line are written back into the spec. - Review has no shared context. Review is a fresh session (ideally a different model) with nothing but the spec and the diff. Findings bounce the work back automatically, capped before it escalates to you. Verify runs the repo's checks plus the spec's own verification plan.
gummi is a single binary. There are no releases yet — install with the Go toolchain (Go 1.26+):
go install github.com/morphis/gummi/cmd/gummi@latestor build from a clone:
git clone https://github.com/morphis/gummi
cd gummi
make build # → bin/gummi (or: go build ./cmd/gummi)For the default agent backend you also need the GitHub Copilot CLI, authenticated:
curl -fsSL https://gh.io/copilot-install | bashRun gummi with no arguments from the root of a git repository:
cd your-repo
gummiFirst run creates the .gummi/ workspace lazily — state directory
(0700), starter config.yaml and profiles.yaml, worktrees directory,
and the ignore rules that keep it all out of your repo's history. Then:
- Press
nand describe the feature — that's the whole creation form; brainstorm develops the rest. The first line becomes the card title; write (or paste) as much as you know past it and it seeds the spec's Problem section, so brainstorm starts from your words instead of a blank page (alt+enterfor a newline). Profile and skip flags sit on a quiet options row. - Press
enterto attach and brainstorm/spec with the architect in the chat pane. Open questions are tracked as a%%checklist in the spec (sto view it;tabtoggles a PR-style annotate mode). - Press
gto advance through gates: approving the spec creates the worktree and branch and settles the spec into.gummi/specs/; approving the plan launches the autonomous implementer. - Watch the running agent (
enter), review the diff (d), and let the review/verify loop run.bbounces work back with your annotations. - Done means a verified branch. Press
mto squash-merge it into main: the dialog drafts a suggested landing message from the spec and the branch's commits; you review, edit, and approve it — nothing is committed until you confirm. Or merge outside gummi — either way it detects the landing (merge or squash-merge) and offers cleanup (c).
Key surfaces on the board (press ? anywhere for the full table):
| key | action |
|---|---|
j/k, pgup/pgdn, 1..9 |
select / jump to a card, or to the first / last card |
enter |
chat (interactive stages) · run / watch (autonomous stages) |
p / t |
pause the running agent, or open the dependency picker on a card with none running / read the session transcript |
s / d |
spec / diff view — tab switches read ⇄ annotate |
g / b |
advance a gate / bounce back to implement or fix |
P |
restore the plan stage on a quick / skip-plan feature (design phase only) |
v |
run the verify checks |
u |
set the budget envelope (credits; 0 = uncapped) |
S |
toggle severity sort (todo only) |
tab / i |
cycle / open the needs-attention inbox |
n / B |
new feature / new bug |
I / G |
ingest a spec doc / import bugs from GitHub issues |
a |
raw-attach the agent CLI in the worktree (escape hatch) |
r / m |
rebase onto main (conflicts hand off to an agent session; verify re-runs after) / squash-merge into main (drafts the message; review & approve, or edit) |
y |
duplicate — a fresh copy starts over in todo, the original stays |
c / x |
clean up a landed branch / delete |
Work rarely starts from a blank line. Two ingestion paths pre-seed the board, both gated on your review before anything is created:
- Spec ingestion (
Ion the board, orgummi ingest <spec-file>) — an architect-role agent decomposes a PRD or design doc into PR-sized feature proposals with a coverage map showing where every requirement went. You rename, edit, merge, or drop proposals, then approve; each one materializes as a pre-seeded draft in todo. - Bug import (
G, orgummi bugs ingest) — deterministic, agent-free import of GitHub issues viagh: one issue → one proposed bug, with a live/filter to narrow a big backlog before approving. External refs are remembered, so re-importing skips bugs already on the board.gummi bugs newadds a single bug by hand.
The board is one way in; the other is a non-interactive driver that runs
the same engine and the same quality floor with no human at the keyboard.
Each gummi run ships one PR-sized feature to a verified branch, streams
milestone + decision NDJSON on stdout, and exits with a typed status — so
a calling agent (or a script) can drive it and branch on the result. It
changes who approves a gate, not whether review and verify run.
run/resume never merge — they stop at a verified branch; landing is the
separate gummi merge verb below.
gummi run --envelope 500 "Add a --format=json flag to the export command"An envelope is required (--envelope N, or GUMMI_ENVELOPE) and an agent
backend must be configured — both fail loud before any work begins. By
default a run takes the quick route (spec → implement → review → verify) and
auto-crosses design gates; --full adds brainstorm + plan, and
--gate-approval=caller hands the design gates back to you via resume.
| command | purpose |
|---|---|
gummi run [flags] "<description>" |
create and drive one feature to a verified branch |
gummi resume <id|ref> [--answer … | --approve | --request-changes …] |
apply a decision and drive on |
gummi status <id|ref> [--json] |
read-only: stage, blockers, spend, branch state |
gummi spec <id|ref> |
read-only: the current spec/report markdown |
gummi diff <id|ref> |
read-only: the worktree diff |
gummi merge <id|ref> -m <message|-> |
land a verified branch as one squash commit (message required) |
gummi clean <id|ref> |
remove a landed card's worktree and branch |
gummi deps add|rm <dependent> <depends-on> / gummi deps list <id> |
manage a card's direct dependency edges |
gummi doctor [--json] [--deep] |
readiness: repo, backend, auth, profile, envelope, lock, per-role reach (--deep) |
gummi skill show|install|list |
generate and install the calling-agent skill |
status/spec/diff take no lock, so you can inspect a feature while a run
is live. A run holds an exclusive .gummi lock, so a headless run and the TUI
never touch the same workspace at once. So do gummi merge and gummi clean,
which mutate the workspace and main.
A run stops at a verified branch — it deliberately never merges, because
the squash-merge landing commit is a review decision. The headless way to make
that decision is gummi merge, which takes the landing message explicitly and
lands it:
gummi merge FD-042 -m "feat(export): add a --format=json flag"
gummi merge FD-042 -m - <<'EOF' # or read the message from stdin
feat(export): add a --format=json flag
The flag writes NDJSON to stdout instead of the table layout.
EOFgummi merge requires the card to be at a verified branch (the same
verified:true state a run stops at), takes no other input, and is stricter
than the TUI's dialog: the message must be a Conventional Commits
type(scope): summary with no diff dump or agent attribution, or the command
refuses with a non-zero exit before touching git. On success it emits a
merged NDJSON event carrying the landed commit's sha and moves the card to
done.
gummi clean <id> is the headless counterpart of the board's c key: it
removes a landed card's worktree and branch, keeping the card as a done entry.
It refuses anything that has not actually landed, or that carries tracked-dirty
rework. Both verbs stream their NDJSON and exit with the same typed statuses as
a run (done = 0, error = 1).
status --json carries two distinct terminal signals. verified:true means
the verify gate passed and the branch is ready to land — the state a
headless run stops at, and the flag a CI caller polls for. done:true means
the branch was actually squash-merged into main (the TUI's m, gummi merge, or a manual land sets it) — so after a headless run, expect
verified:true with done:false until you merge.
Every run/resume ends on a typed exit the caller branches on:
| exit | status | caller action |
|---|---|---|
0 |
done |
verified branch ready — report it, stop |
0 |
stopped |
--until reached its clean stop — resume --approve to continue |
2 |
question |
a delegated question or caller gate — resume --answer/--approve/--request-changes |
3 |
blocked |
open %%/diff threads block a gate (resolve, or resume --request-changes), or an unmet dependency blocks coding-stage entry (blocking_deps on the event — wait for it to land, or edit the edge with gummi deps rm) |
4 |
escalation |
a rerun/critique cap or unclear verdict — report to a human; resumable |
5 |
exhausted |
envelope dry — raise it, then resume |
6 |
timeout |
a stage went quiet (likely hang) — report; resumable |
1 |
error |
setup/agent failure — nothing partial landed |
Useful run flags: --ref <id> correlates a feature with your own tracker
(and lets status/resume look it up by that id), --acceptance <file|->
seeds the spec's verification plan, --until spec stops cleanly for a human
design review before implementation burns tokens, and --autonomous
auto-takes the recommended answer instead of checkpointing questions.
A card can declare a direct dependency on another with gummi deps add <dependent> <depends-on> (gummi deps rm/list remove or read them back).
A dependency counts as met only once the target reaches done — its branch
is verified and landed — so anything short of that blocks the dependent
card's entry into its coding stage (Implement/Fix) with a blocked
status naming the outstanding card(s). The TUI exposes the same edges via the
p key's dependency picker (on a card with nothing running) and shows live
per-dependency status on the board and spec view. gummi ingest also seeds
edges automatically when a decomposed spec calls one proposal out as
depending on another.
gummi generates its own skill — a SKILL.md documenting this loop — and
installs it where Claude Code, GitHub Copilot CLI, Codex, and opencode read it:
gummi skill install # project scope: shared .claude/skills + Codex .agents/skills
gummi doctor # then check the backend/auth/envelope are readyA project-scope install writes .claude/skills/gummi/SKILL.md for Claude,
Copilot, and opencode, plus .agents/skills/gummi/SKILL.md for Codex.
--scope user writes to each detected agent's home instead, and --agent
targets one ($HOME/.agents/skills for Codex). The doc's command grammar and
exit table are generated from the binary's real flags, so they can't drift;
the frontmatter is version-stamped,
so install/list detect a stale or edited file and refuse to overwrite it
without --force. gummi skill show prints the rendered doc.
gummi doctor is the readiness check the skill's first-run setup runs (--json
for the machine-readable checklist). It reports; it never repairs auth or
writes secrets — a backend needing login is surfaced as the exact command for
a human to run. Provider config (endpoints, API keys) lives in each backend's
native store (Claude Code login, codex login, opencode auth, environment for headless),
never in profiles.yaml.
The agent layer is pluggable. GUMMI_AGENT selects the default backend;
a profiles.yaml role can pick a specific backend via its backend:
field (see Configuration below) so one session can mix providers — e.g.
copilot for implement, claude for review.
- copilot (default) — the official Copilot SDK for Go, driving the
copilotCLI in server mode. Full duplex: streaming, tool-call visibility, client tools, session resume. - claude — the Claude Code CLI in streaming print mode
(
GUMMI_CLAUDE_BINoverrides the binary). Requirespermissions: allow-all— guarded mode is rejected because the CLI's default permission mode silently auto-denies tools. Claude Code manages its own endpoint routing via its native config (ANTHROPIC_BASE_URL, Claude Code login). - opencode — the opencode CLI (
GUMMI_OPENCODE_BINoverrides the binary). Provider/model config is owned by opencode itself (opencode auth,opencode.json). - codex — the Codex CLI (
GUMMI_CODEX_BINoverrides the binary), using stablecodex exec --jsonJSONL turns andcodex exec resumewhile the gummi session remains live. Codex owns authentication (codex login) and provider configuration; gummi passes the profile's model through Codex's native-mflag and never copies credentials or writes Codex config. This backend requirespermissions: allow-all: the stable exec stream cannot service guarded approval callbacks. Messages appear when Codex completes each message item, while command, file-change, and MCP activity remains visible as tool events. - headless — a generic subprocess adapter for any agent binary
speaking a small stdio JSON protocol (
GUMMI_AGENT_CMDis its command line). The child inherits gummi's environment and reads its own provider config from there. SetGUMMI_HEADLESS_CREDITS_PER_1Kto price a local endpoint's token spend into credits so it meters against the same budget envelope.
No usable agent just leaves the board static — creation, specs, worktrees, and gates all still work.
Two files in .gummi/, both scaffolded on first run:
config.yaml— the permission mode:allow-all(default — gummi assumes it runs in a sandbox) orguarded(agent tool calls need approval through the inbox). The per-profilesandbox: enforce|warn|offconfinement and the main-checkout tripwire back the allow-all default; see DESIGN §4.4. The Verify stage's check commands are not configured here: gummi auto-discovers the repo's build/test/lint commands at approval into each spec's Verification plan (agummi-checksblock), where you review and edit them — and the TUI still surfaces the exact commands before running them.profiles.yaml— role →{backend, model}maps per profile (premium,thrifty, …) with a declared default.backend:is optional (copilot|claude|codex|opencode|headless); omitted, the role uses whateverGUMMI_AGENTselects. This lets a single profile mix providers — e.g.implementer: copilot,reviewer: claude— and keeps all provider config (endpoints, keys, credit rates) out of the repo-committed file.
Environment variables:
| variable | effect |
|---|---|
GUMMI_AGENT |
default backend: copilot (default) · claude · codex · opencode · headless |
GUMMI_AGENT_CMD |
headless adapter's command line |
GUMMI_CLAUDE_BIN |
claude backend's binary (default claude on PATH) |
GUMMI_CODEX_BIN |
codex backend's binary (default codex on PATH) |
GUMMI_OPENCODE_BIN |
opencode backend's binary (default opencode on PATH) |
GUMMI_HEADLESS_CREDITS_PER_1K |
headless adapter's token→credit rate for a local endpoint (llama.cpp, vLLM); 0 uses the engine default |
GUMMI_MODEL |
fallback model when a role isn't covered by a profile |
GUMMI_MAX_ACTIVE |
concurrent autonomous sessions (default 1) |
GUMMI_ENVELOPE |
default credit envelope for new features; also a floor under the estimated envelope — the scribe/history blend may raise it, never undercut it |
GUMMI_STAGE_BUDGET |
flat per-stage credit cap |
GUMMI_TURN_RESERVE |
one turn's credits — the floor under envelope-derived stage budgets (default domain.TurnReserveCredits; override for unusual models) |
GUMMI_COPILOT_HINT |
off hides the status-bar Copilot quota pill (on by default; needs an authenticated gh CLI to show anything) |
GUMMI_THEME |
dark (default) · light · neon |
GUMMI_NOTIFY |
needs-attention hook: bell (default) · desktop · off |
GUMMI_ATTACH_CMD |
command for raw-attach (default: selected backend's CLI) |
make demo # creates a throwaway repo with gummi initialized
make e2e # scripted TUI drive asserting the full lifecycle (needs tmux)make build # build bin/gummi
make test # run all tests
make lint # go vet + golangci-lint
make golden-update # regenerate UI golden files
make ci # build + test + lintdocs/DESIGN.md is the design document — its Decisions list is binding.
scripts/record-demo.sh regenerates the README demo GIF (needs tmux,
vhs, ttyd, and ffmpeg): it seeds
a throwaway repo through the real TUI and records a scripted drive.
