marver 0.0.20

A TUI workspace for AI agent sessions: tmux orchestration, git worktree management, and repo control in one place.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
# 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 —
> describe a task, an agent runs it, review the diff, commit — but nothing has
> been used in anger yet. The published crate is still a name reservation.

```
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)
```

Every command takes `--data-dir` for where the database, socket, and log live,
`--scan-root` for the directory searched for repos, `--cap` for how many agents
may run at once, and `--harness` for which agent to run. The interface and the
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 the server it needs — and that daemon keeps running after you
close the interface. It owns the queue: it creates a worktree per repo, opens a
tmux session, starts the agent with its reporting pointed back at itself, and
notifies you when a task finishes or gets stuck. Its output goes to `daemon.log` beside
the database.

Run `marver daemon` yourself to watch it in the foreground or to put it under a
supervisor. Starting a second one is refused rather than allowed to displace the
first.

## Keys

Three rules hold everywhere:

- **`esc` leaves the screen you are on.** On the task list there is nothing
  underneath, so leaving is quitting.
- **`ctrl-c` and `ctrl-q` also leave**, from anywhere, including inside a text
  field — where `ctrl-c` drops what is being typed rather than the screen behind
  it, because the field is what the key is being pressed at. The agent's
  terminal is the exception for `ctrl-c`: there it goes to the agent, which is
  the one place that reflex means something else. `ctrl-q` leaves even there.
- **`x` discards.** A task on the list, a diff in review. It is never `c`, which
  means commit.

No other chord does anything. A modified key is not a letter binding, so `ctrl-x`
in the review screen is not `x` and does not type an `x` into a commit message
either.

The footer of every screen shows its own keys; the whole set is below.

### Task list

| Key | |
|---|---|
| `j` `k` · `` `` | move |
| `g` `G` · `Home` `End` | first, last |
| `` | open the agent's terminal |
| `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, when nothing else will |
| `t` | its todos — `tab` there reaches the global ones |
| `x` | cancel it; press again once it has ended to archive it |
| `u` | put an archived task back on the list |
| `s` | cycle order — newest, oldest, by repo |
| `a` | cycle what is shown — open, all, archived |
| `r` | refresh now |
| `R` | the repos, and where each one stands |
| `esc` `q` | quit |

### New task

| Key | |
|---|---|
| `tab` | next field: repos → prompt → title |
| `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 |

You land on the repos, because that is the order the decisions happen in: where
the work goes, then what to do, then — only if you want to say — what to call it.

**The title is optional.** Left blank, the task is named after the first line of
the prompt, cut at a word; the field shows the name it is about to take, dimmed,
so it is an offer rather than a surprise found later in the list. Typing replaces
it. A prompt with nothing nameable in it — a pasted stack trace, a line of
punctuation — leaves the task named after its repos, which at least says where it
is.

`↵` inserts a newline in the prompt rather than queueing. A prompt is what the
agent is told to do and is routinely several paragraphs, so the only way to type
a newline is worth more there than the shortcut; `ctrl-s` is how you submit
without leaving the field.

### Agent terminal

Every key goes to the agent, which is the point of the screen, so leaving takes a
chord.

| Key | |
|---|---|
| `ctrl-q` | back to the list |
| `ctrl-esc` | back to the list, where the terminal can say it — see below |
| `` | sent to the agent, and returns a blocked or reviewed task to `running` |

`esc` is not a way out here: it is how Claude is interrupted, and the agent needs
it. `ctrl-esc` is the binding this screen would like to have, but a terminal can
only report it under the kitty keyboard protocol, which marver does not enable —
turning it on changes how every key is reported, and this screen forwards every
key through a hand-built encoding table. In Terminal.app and iTerm2 it will not
arrive at all. **`ctrl-q` is the one that always works**, in every terminal,
because raw mode clears `IXON` and so nothing upstream takes it for flow control.

### Todos

| Key | |
|---|---|
| `a` | add one — `` to keep it, `esc` to drop it |
| `` | 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 |

### Repos

| Key | |
|---|---|
| `j` `k` · `` `` | move |
| `g` `G` · `Home` `End` | first, last |
| `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 |
| `c` | commit — type a message, `` to confirm, `esc` to cancel |
| `x` | reject — type instructions to the agent, `` to send |
| `r` | refresh |
| `esc` `q` | back |

Rejecting types your instructions into the agent's live session and returns the
task to `running`. Instructions containing control characters are refused:
`send-keys -l` stops tmux reading them, but the agent still sees the bytes, and
`\r` would submit half a sentence while `\x03` would interrupt the very turn
being asked for.

## Upgrading

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

`marver upgrade` drives cargo, because that is how the crate is distributed. If
you installed marver some other way it says so rather than guessing, and you
upgrade it however you installed it.

Installing a new marver does not replace a daemon that is already running: it
keeps executing the old binary, and since it still holds the socket, nothing
starts a replacement. This is deliberate — that daemon is supervising live
agents, and restarting it underneath them would fail every task it was watching.

`marver status` reports both versions and says so when they differ, and
`marver restart` does the swap when you are ready:

```
marver restart
```

It refuses while any task is `running` or `blocked`, because that is the one
thing a restart can cost. `marver hook` always exits 0 — marver being down must
never interfere with an agent — so a hook arriving while nothing is listening is
simply lost, and the task is left `running` behind an agent that has already
finished. Wait for those tasks to reach `awaiting-review`, or accept it with
`marver restart --force`.

Nothing restarts the daemon on your behalf. It is supervising live agents, and
the moment to interrupt that belongs to whoever knows what they are in the
middle of. Agents already running in tmux survive a restart either way.

## What an agent has spent

A task's screen shows what its agent is costing you, beside the repo and branch:

```
 running  api  marver/4-fix-auth  opus-5 · 310k ctx · 334k out
```

Three things, and the two numbers mean different things. **`ctx`** is a *level* —
how full the context window was on the most recent turn — and **`out`** is a
*total*, output tokens across every turn since the task started. Summing the
first or replacing the second would both be wrong, and both would look plausible.

Nothing is shown until the agent has answered once. Unknown is not zero: a row of
zeroes would claim the agent had run and produced nothing.

**There is no cost figure, deliberately.** None of this is in the hook payloads —
the only source is the transcript JSONL that Claude Code writes, and it records
no price of any kind. A number in dollars could only come from marver
multiplying tokens by a price table it hardcoded, which would be confidently
wrong the day prices moved. Tokens are what the transcript knows, so tokens are
what marver reports.

That transcript is an internal format with no compatibility promise, which is why
this whole feature is built to fail quietly: every field is optional, unparseable
lines are skipped, and a transcript that stops making sense costs a task its
numbers and nothing else. Reading is incremental — each hook reads only the bytes
added since the last one — so watching a long task does not get more expensive as
it runs.

## The repos

`R` on the task list opens the other half of the picture. The task list says
what the agents are doing; this says what they would be starting from:

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

Four things per repo, and the two middle columns are different questions.
**upstream** is how far the checked-out branch has drifted from the branch it
tracks. **default** is how far it is from `origin/main` — the one that says
whether starting a task here would branch from something stale. It is left blank
when it would only repeat the upstream column, which is the usual case on `main`
itself, because a number printed twice reads as two facts.

Ahead and behind are always both counted and only the non-zero one is shown.
Behind is coloured and ahead is not: work you did is work you know about, and the
number worth noticing is the one that appeared while you were not looking.

`f` fetches the repo under the cursor and `p` fast-forwards it. Both are as
timid as they sound, deliberately, because they run on one keystroke against a
repo you are not looking at and possibly one an agent is working in:

- **`f` is a plain `git fetch`** — no `--prune`. It adds remote-tracking refs
  and touches no branch and no working tree.
- **`p` is `git pull --ff-only`.** Where a merge would have left conflict
  markers in a working tree nobody asked to touch, and a rebase would have
  rewritten commits underneath an agent, this refuses to move and says so.

Neither blocks the interface. Reading one row costs several git subprocesses and
a fetch costs a round trip to a host that may be slow, unreachable, or waiting
for a passphrase, so the work happens on a thread and the rows fill in as they
arrive — which is also why a screen of thirty repos is readable before it is
finished. One job runs at a time; a second key press is told what is already
running rather than queued behind it.

The list is the last scan's, not a fresh walk of the disk. The daemon owns
scanning, and a screen that went looking on its own would disagree with the repo
picker on the new-task screen. A repo that has moved since keeps its row and says
`gone from disk` rather than quietly vanishing.

## Other 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
```

Worktrees, a queue, a tmux 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: without it a task sits in `running` behind an agent that stopped an hour
ago, holding a slot.

So a harness is defined by what it can say, and marver does not pretend
otherwise:

| | 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 |

**codex has exactly one event.** `agent-turn-complete` is the whole of it, so a
codex task moves to `awaiting-review` on its own but never enters `blocked` —
when it stops to ask you something, marver still believes it is working. There
are no token figures either: those come from Claude Code's transcript, and codex
does not write one marver reads.

**Anything else is started and then watched.** You get the queue, the worktree,
the session, the diff, the todos, and the terminal; what you do not get is a
task that finishes by itself. `f` on the task list is how you say so.

Neither of the two has its config edited. Claude Code takes `--settings` and
codex takes `--config notify=[…]`, so the reporting is wired **on the command
line of that one task** — marver never writes to `~/.claude` or `~/.codex`. A
tool that edits your global config to run one task has to be trusted to undo it,
and a crash in between leaves you notifying a daemon about tasks that no longer
exist.

`f` is worth knowing about even on Claude Code. A `Stop` that arrives while the
daemon is down is simply gone — `marver hook` always exits 0, deliberately — and
`f` is how that task gets to `awaiting-review` without waiting for something
that is never coming. Like pausing, it asserts rather than observes: nothing is
sent to the agent and the agent is not asked.

## Pausing

`p` on the task list sets a task aside, and `p` again picks it back up. One key
in both directions, because from there you are deciding whether to look at
something now or later, not which state it is in.

What it does depends on where the task is, and you should know which:

- **A task that has not started** is simply held out of the queue. The scheduler
  passes over it and starts the next one instead. Resuming puts it back in the
  queue, subject to the cap like anything else.
- **A task with a live agent**`running` or `blocked` — is *interrupted*.
  marver sends the agent `esc`, which is how Claude is stopped mid-turn, and
  frees the concurrency slot so something else can run. The tmux session stays
  alive, so nothing is lost.

Which one you get is decided by whether the task has a session, and where
resuming returns it to is decided the same way. Nothing records where it came
from, so nothing can be wrong about it.

Two things worth knowing about the second case. **Nothing confirms the
interrupt** — Claude Code's hooks report what an agent does, not what is done to
it — so pausing asserts a state rather than observing one. And because of that,
**resuming types `continue` into the session** rather than only setting the state
back: an agent sitting idle behind a task marver believes is `running` would
never produce the `Stop` that finishes it. It is a real prompt to a real agent,
not an internal signal.

Pausing happens before the state moves, so a session that cannot be reached
leaves the task exactly where it was, and says so.

## Todos

`t` opens the todos for the task under the cursor; `tab` there switches to the
global list. They are the same screen and the same keys, and they differ in what
`↵` means — which is the only thing a todo is for:

- **A task's todo** is a note for an agent that already exists. `` types it into
  that agent's session and ticks it off. If it could not be delivered it is left
  undone, since losing the note as well as the attempt would be worse.
- **A global todo** is something you have not scoped yet. `` opens the new-task
  screen with the text as the prompt, leaving you to pick the repos — and since
  the title is written from the prompt, that is the only thing left to do. It is
  ticked off when the task is actually queued, not when the screen opens — a task
  you thought better of leaves the note outstanding.

`space` ticks one off and leaves it in the list; `x` deletes it. Two keys because
they mean different things, and a list that hid the finished ones would make the
first look like the second.

## What a task was asked to do

`y` on the task list puts the brief over the row:

```
┌ task 1 ──────────────────────────────────────────────────────────┐
│ running  Make fetch work on every repo                           │
│api                                                               │
│described 2026-08-08 21:28 · 1h ago                               │
│started   2026-08-08 21:32 · 1h ago                               │
│                                                                  │
│Prompt                                                            │
│The repo screen fetches one repo at a time.                       │
│                                                                  │
│Add a key that fetches every repo at once, reporting each one as   │
│it lands rather than waiting for all of them.                     │
└ any key closes ──────────────────────────────────────────────────┘
```

**Described and started are two different times**, and the gap between them is
the queue — which is the thing you are looking for when a task has not begun.
Started is read from the event log rather than a column: a task that was paused
and resumed started once, and `updated_at` moves with every hook.

Whatever you press next closes it and does nothing else. A popup you have to
learn how to close is a popup that gets in the way, and one whose dismissing key
also acted would make reading it a hazard — `x` is right there.

The same brief is written to **`task.md` in the task's workspace** when it
launches, beside the worktrees, so the directory says what it is for. The agent
can read it; so can you, a month later, having found a folder of changes with
nothing to explain them. It is written once and never updated — it records what
the task was *asked* to do, which does not change. What it went on to do is what
the diff and the list are for, and a file that tried to track state would be
wrong between every hook.

The popup reads the store rather than that file, since a task that has not
launched has a prompt and no directory yet. Writing the file is best effort: a
note about the work must never be the reason the work does not happen.

## Archiving

`x` discards the task under the cursor, and what that means depends on whether
it is still going:

- **A task that is still going** is cancelled. It stays on the list, because a
  task you just stopped is one you may still want to look at — the diff it left,
  the reason it went wrong.
- **A task that has ended** is archived: its tmux session is killed and the row
  leaves the list. `a` cycles round to the archive when you want it back.

Two presses of one key, in the order the thoughts occur: *not this*, then *and
take it off my screen*. No single press does both, so nothing you press once can
end a task and hide it in the same movement.

**Archived is not a state.** It is a date on the row, beside the state the task
ended in, and the archive shows that state as the list does. Making it a state
would answer "is it out of the way" by throwing away "how did it end", and a
cancelled task and a committed one do not read the same six weeks later.

Killing the session is the point of doing this from here rather than waiting.
The daemon reaps the sessions of finished tasks on its own pass — but only while
it is running, and a row that has just left the list is the worst moment to
leave an agent alive behind it. Archiving is therefore also the answer to "that
session is still open and there is no daemon". If the session cannot be killed
the task stays on the list and says why, because a row going quiet while its
agent kept running is the one outcome nobody could have deduced.

**Worktrees are not touched**, here or by cancelling — see below. `marver
cleanup` still finds archived tasks, since archiving changes nothing about the
state it selects on.

## Cleaning up

A task keeps its worktrees after it ends. Cancelling records that it is over and
archiving kills its session, and both stop there deliberately — `git worktree
remove --force` discards uncommitted changes, and losing unreviewed work as a
side effect of tidying a list would be worse than a directory left on disk.

`marver cleanup` is where that gets undone, as a separate act:

```
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. Anything else is
in use: a running agent is working in its worktree, and an `awaiting-review` task
is what the review screen reads its diff from.

A worktree still holding changes is **listed and left alone**, because "finished"
is a state marver recorded and not a promise about what is in the directory. It
says which ones and what to type if you meant it.

Branches are kept by default. marver commits onto `marver/<id>-<slug>` and merges
it nowhere, so for a committed task that branch holds the only copy of the work.
`--branches` deletes with `git branch -d`, which refuses any branch whose commits
exist nowhere else — so the branch of a task that did nothing is tidied away and
the branch of one that did something survives, and cleanup says how many it kept.
`--force --branches` deletes those too.

Nothing runs this on a schedule.

See [ARCHITECTURE.md](ARCHITECTURE.md) for the design and the reasoning behind
it.

## 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 |
| daemon, five hours of real use | 0.06% | 7.2 MB |
| interface, task list open | 0.01% | 8.2 MB |
| interface, agent's terminal, quiet | 0.2% | |
| interface, agent's terminal, agent working | 1.8% | |
| interface, agent's terminal, 9 MB/s of output | 31% | |

**The daemon does nothing between events.** It blocks on its socket with a 2s
timeout, so an idle one is a few queries a minute; the repo scan it runs every
60 seconds takes 70µs over five repos, because the depth limit means it looks at
tens of directories rather than thousands.

**The interface costs what is moving.** The task list re-reads the store four
times a second — 0.03ms for ten tasks, 0.5ms for a thousand — and draws only
when the answer differs from last time, which while nobody is typing it never
does. The agent's terminal is the expensive screen, and the expense is the VT
parser: at 9 MB/s it is 31% of a core, of which drawing is 0.7 and parsing is all
the rest. Nothing an agent does approaches that rate — Claude Code repainting its
interface as it works measured 1.8%, and most of that is the millisecond polling
that makes typing feel direct rather than relayed.

The 1000-task figure is worth knowing rather than worrying about: it is 0.2% of a
core, and `a` on the task list hides finished tasks by default precisely so the
list stays a worklist.

## Known gaps

Written down because finding them yourself is worse.

**The whole loop has never been run end to end.** Every part is tested — 570-odd
tests, several driving real tmux servers, real git repos, and the real binary —
and the library has been exercised from outside. But *describe a task → agent
works → review the diff → commit* has not been completed in one pass by a person.
Expect to be the first.

**Nothing reclaims a worktree unless you ask.** `marver cleanup` exists — see
[Cleaning up](#cleaning-up) — but there is no key for it inside the interface and
nothing runs it on a schedule, so a marver left alone accumulates directories and
branches until you type the command.

**Task ids name tmux sessions, and a tmux server is machine-wide.** Two data
directories that each hold a task 1 both want the session `marver-1`, so the
second one to launch is refused with "session already exists" and its task fails.
Cleanup no longer kills across that boundary — it checks the session's working
directory first — but launching is still first-come-first-served. One data
directory per machine is the supported arrangement.

**A hook that arrives while the daemon is down is lost.** `marver hook` always
exits 0, deliberately, so marver being unavailable never interferes with an
agent — which means a `Stop` landing in the gap is simply gone, and the task is
left `running` behind an agent that has already finished. Startup reconciliation
does not rescue it, because it only fails tasks whose tmux session has died and
that session is still there. This is why `marver restart` refuses while anything
is `running` or `blocked`.

**An old daemon keeps serving after an upgrade.** By design — it is supervising
live agents. `marver status` warns and `marver restart` swaps it, but nothing
forces the issue, and a future schema migration could have a new interface
migrating a database an old daemon still has open.

**A codex task never blocks, and shows no tokens.** Codex reports one event —
`agent-turn-complete` — so a codex agent that stops to ask you something leaves
its task in `running`, and the token figures come from a transcript only Claude
Code writes. `f` on the task list is how a task under any harness reaches
`awaiting-review` when its agent cannot say so. See [Other agents](#other-agents).

**A harness marver does not know is started and then only watched.** Everything
except the reporting works. Nothing infers that an agent has finished from what
its pane printed, and nothing will: guessing at that and being wrong would move
tasks under you.

**`ctrl-esc` does not arrive in most terminals.** See [Keys](#agent-terminal).
`ctrl-q` always does.

**A multi-repo commit is not atomic.** One message becomes one commit per repo,
and if the second fails the first still stands. The task then stays in
`awaiting-review` with part of its work committed.

**Status messages last 500ms**, errors included, which is not long to read one.

**Invalid option *values* are ignored silently.** Unknown options are refused,
but `--cap notanumber` falls back to the default without saying so.

**`list_events` cannot reach events with no task.** Scans log against no task id,
and the only query takes one — so those rows are written and never read back.

**Nothing confirms that a pause reached the agent.** Claude Code reports what an
agent does, not what is done to it, so `paused` is asserted rather than observed.
If the interrupt did not land, the agent finishes its turn and the `Stop` hook
arrives for a task marver has already moved — the transition is refused and the
task stays `paused` with work quietly completed behind it. Resuming and reading
the pane is the way out.

**Upgrading past 0.0.16 changes the schema, and so does archiving.** The first
new marver to open the database migrates it, and an old daemon may still be
running against it. 0.0.16 added `paused`, which an old daemon never asks for but
cannot read in a hook; the archive adds a column, which an old daemon ignores
entirely. `marver restart` after upgrading either way.

**Token figures depend on a format nobody promised to keep.** marver reads
Claude Code's transcript JSONL for the model and token counts — see [What an
agent has spent](#what-an-agent-has-spent). If that file changes shape the
numbers stop appearing. Nothing else depends on them, and no cost is shown at
all, because the transcript records no price to show.

## Requirements

`tmux` 3.x, `git`, and Claude Code.

## Name

A *marver* is the polished steel table a glassblower rolls hot glass across —
shaping the piece and controlling its temperature between trips to the furnace.
It is the surface where work takes form.

## License

MIT OR Apache-2.0, at your option.