marver 0.0.25

A TUI workspace for AI agent sessions: tmux orchestration, git worktree management, and repo control in one place.
Documentation

marver

A TUI workspace for AI agent sessions — tmux orchestration, git worktree management, and repo control in one place.

Start a task, describe it, and marver opens a tmux session with an agent already working in its own worktree. Inspired by k9s, cmux, and Warp.

Status: early development. Every subsystem exists and the loop closes, but nothing has been used in anger yet. Expect to be the first.

marver                  open the interface, starting a daemon if none is running
marver status           report whether a daemon is running, and what it holds
marver restart          stop the running daemon and start this version
marver upgrade          install the newest published marver
marver cleanup          remove the worktrees left by finished tasks
marver daemon           run the scheduler and hook receiver in the foreground
marver scan [root]      list the git repos it can see
marver --version        print the version (-v, -V)
marver --help           print usage (-h)

Options, on every command: --data-dir (database, socket, log), --scan-root (where repos are looked for), --cap (how many agents run at once), --harness (which agent to run). The interface and daemon find each other through those paths, so a --data-dir given to one must be given to the other.

Just run marver. If no daemon is listening it starts one, detached, the way a tmux client starts its server — and that daemon keeps running after you close the interface. It owns the queue: a worktree per repo, a tmux session, an agent, and a notification when a task finishes or gets stuck.

Keys

Three rules hold everywhere:

  • esc leaves the screen. On the task list that means quitting.
  • ctrl-c and ctrl-q also leavectrl-c drops the text being typed if a field is open. In the agent's terminal ctrl-c goes to the agent; ctrl-q still leaves.
  • x discards, c commits. Never the other way round.
  • / filters the list you are on, on every screen that has one. Typing narrows as you go, keeps it, esc backs out — the field first, then the filter, then the screen.

No other chord does anything: a modified key is not a letter binding.

Task list

Key
j k · move
g G · Home End first, last
open the agent's terminal
/ filter by title, repo, state, or prompt
y what it was asked to do — any key closes it
v review the diff
n new task
p pause it, or resume it
f say its agent has finished
t its todos — tab there reaches the global ones
x cancel it; again once it has ended to archive it
X remove a finished task's worktrees — twice if there is work to lose
u put an archived task back
s cycle order — newest, oldest, by repo
a cycle what is shown — open, all, archived
r refresh
R the repos
esc q quit

New task

Key
tab next field: repos → prompt → title
/ filter the repo list
space select a repo
j k move through repos
queue it — from the repo list or the title
ctrl-s queue it from anywhere, including inside the prompt
esc discard

types a newline in the prompt rather than queueing, since a prompt is routinely several paragraphs. The title is optional — left blank it is the first line of the prompt, cut at a word, shown dimmed before it is taken.

Agent terminal

Key
ctrl-q back to the list
ctrl-esc back to the list, where the terminal can send it
to the agent, and returns a blocked or reviewed task to running

Every other key goes to the agent, esc included — it is how Claude is interrupted. ctrl-esc needs the kitty keyboard protocol and will not arrive in Terminal.app or iTerm2. ctrl-q always works.

Todos

Key
a add one — keeps it, esc drops it
/ filter them
use it: sends it to the agent, or starts a task from it
space tick it off, leaving it in the list
x delete it
tab switch between this task's todos and the global ones
esc q back

A task's todo is typed into that agent's session. A global one opens a new task with the text as its prompt, and is ticked off only once the task is queued.

Repos

Key
j k · move
g G · Home End first, last
/ filter by name or branch
f fetch it
p fast-forward it
r read them all again
esc q back

Review

Key
j k · move between files
space s stage or unstage the file
a stage everything
tab switch between the staged and working-tree diff
ctrl-d ctrl-u scroll the diff
/ filter the files
c commit — type a message, to confirm
x reject — type instructions to the agent, to send
r refresh
esc q back

Rejecting types your instructions into the agent's session and returns the task to running. Instructions containing control characters are refused: \r would submit half a sentence, \x03 would interrupt the turn being asked for.

Filtering

/ on any list narrows it as you type, k9s-style. keeps the filter and gives the keys back, esc drops what you typed, esc again clears the filter, and only then does it leave the screen.

It searches more than the row shows: a task matches on its title, repo, state, or prompt, so /auth finds the task you described that way even when the title says something else. The count in the header says what is hidden — Tasks (2 of 4 open) · /auth.

Two details worth knowing. On the new-task screen only the repo list filters, since in the prompt / is a character you are typing — and a repo you chose stays chosen when the filter hides it. On the repo screen the filter narrows what you see and not what is being fetched, so a job already running still finishes for every repo.

Agents

marver --harness claude                Claude Code (the default)
marver --harness codex                 OpenAI's codex
marver --harness opencode:opencode     anything else, by name and command
marver --harness aider:aider --yes     with arguments, if it needs them

Quote an argument that contains a space — --harness 'x:prog --mode "go fast"' — and it stays one argument all the way to the agent.

Worktrees, a queue, a session, a diff to review: none of that is specific to Claude Code. Being told what the agent did is, and it is what the states run on. So a harness is defined by what it can say:

how it reports what marver learns
claude a settings file of hooks started, finished, blocked, and why
codex its notify program finished a turn
anything else nothing nothing

A codex task never blocksagent-turn-complete is its only event — and shows no token figures, which come from a transcript only Claude Code writes. Anything else is started and then watched: everything works except a task that finishes by itself. f on the task list is how you say it has.

Neither has its config edited. Claude Code takes --settings and codex takes --config notify=[…], so reporting is wired on that one task's command line — marver never writes to ~/.claude or ~/.codex.

f earns its place on Claude Code too: a Stop arriving while the daemon is down is simply gone, since marver hook always exits 0.

The repos

R shows what every scanned repo is standing at — what a new task would start from:

 repo        branch              upstream       default          changes
 api         main                up to date                      clean
 marver      release-prep        ↑2 ↓1          ↓12 origin/main   3 files
 notes       detached                                            1 file
 old-thing   gone from disk

upstream is the gap to the branch this one tracks; default is the gap to origin/main, which says whether starting here would branch from something stale. The second is blank when it would only repeat the first. Behind is coloured, ahead is not.

f is a plain git fetch (no --prune) and p is git pull --ff-only, both deliberately timid: they run on one keystroke against a repo you are not looking at and possibly one an agent is working in. Neither blocks the interface — rows fill in as they arrive, one job at a time.

The list comes from the daemon's last scan, not a fresh walk, so it agrees with the repo picker on the new-task screen.

Tasks

y puts a task's brief over the row: repos, branch, when it was described and when it started (two different times — the gap is the queue), and the prompt in full. The same brief is written to task.md in the task's workspace when it launches, so the directory says what it is for.

p sets a task aside and picks it up again. One that never started is held out of the queue; one with a live agent is interrupted with esc and frees its slot. Nothing confirms the interrupt — Claude reports what an agent does, not what is done to it — so resuming types continue into the session rather than only setting the state back.

x cancels a task that is still going. Pressing it again once the task has ended kills its tmux session and archives the row; a cycles round to the archive and u brings one back. Archived is a date beside the state it ended in, not a state of its own.

Tokens. A task's screen shows opus-5 · 310k ctx · 334k out beside the repo. ctx is a level — how full the window was on the last turn — and out is a total across every turn. Nothing is shown until the agent has answered once, because unknown is not zero. There is deliberately no cost figure: the transcript records no prices, so one could only come from a hardcoded table.

Cleaning up

A task keeps its worktrees after it ends — git worktree remove --force discards uncommitted changes, and losing unreviewed work while tidying a list would be worse than a directory left on disk.

marver cleanup              remove the worktrees of finished tasks
marver cleanup --dry-run    list what would go, change nothing
marver cleanup --branches   delete their branches too, where merged
marver cleanup --force      remove work that was never committed

Only committed, failed, and cancelled tasks are offered; a worktree still holding changes is listed and left alone. Branches are kept by default, since for a committed task that branch holds the only copy of the work. Nothing runs this on a schedule.

Upgrading

marver upgrade              # cargo install marver --force
marver upgrade --restart    # and swap the daemon too

Installing does not replace a running daemon: it keeps executing the old binary and still holds the socket. That is deliberate — it is supervising live agents. marver status says when the versions differ and marver restart swaps it, refusing while any task is running or blocked. Agents in tmux survive a restart either way.

What it costs

Measured on an M-series Mac, marver 0.0.20, release build. Percentages are of one core.

CPU Memory
daemon, idle 0.03% 7.4 MB
interface, task list 0.01% 8.2 MB
interface, agent's terminal, agent working 1.8%
interface, 9 MB/s of pane output 31%

The daemon does nothing between events; the 60-second repo scan takes 70µs over five repos. The interface draws only when something changed. The expensive case is the VT parser, and nothing a real agent does approaches that rate.

Known gaps

  • The whole loop has never been run end to end by a person, though every part is tested — 600-odd tests, several driving real tmux servers and repos.
  • A dismissed permission prompt does not unblock a task. Claude Code emits a hook when you type a reply, not when you answer a dialog, so a task blocked on a permission prompt stays blocked until the agent finishes.
  • Nothing reclaims a worktree on a schedule. X on the task list removes the selected finished task's worktrees and marver cleanup sweeps them all, but neither happens by itself.
  • Two data directories cannot share a repo. Sessions carry a tag for the data directory that made them, so two marvers on one machine no longer fight over marver-1 — but branches are still named marver/<id>-<title>, so two of them starting the same-numbered task against the same repo collide on the branch.
  • A hook arriving while the daemon is down is lostmarver hook always exits 0 by design. f on the task list is the way out.
  • An old daemon keeps serving after an upgrade, by design. marver restart.
  • A codex task never blocks and shows no tokens; an unknown harness reports nothing at all.
  • A multi-repo commit is one commit per repo, sharing a message. If a later repo fails, the earlier ones are put back with reset --soft, so the task commits all or nothing — unless the undo itself fails, which it says.
  • Status messages last 500ms, errors included.
  • Invalid option values are ignored silently, except --harness.
  • Token figures depend on a format nobody promised to keep, so they are built to fail quietly.

Upgrading migrates the schema. An old daemon keeps working against a migrated database, but restart it when convenient.

See ARCHITECTURE.md for the design and the reasoning behind it.

Requirements

tmux 3.x, git, and an agent — Claude Code, codex, or your own.

Building it needs Rust 1.88 or newer, for let-chains and for what ratatui asks for. An older toolchain fails partway through the dependency graph rather than at the manifest, so rustup update is the fix.

Name

A marver is the polished steel table a glassblower rolls hot glass across, shaping the piece between trips to the furnace. It is the surface where work takes form.

License

MIT OR Apache-2.0, at your option.