A review loop that closes.

Agentic development turns you into a reviewer. Most terminal review tools stop at an export you paste into a chat window. margin keeps the review open, and the agent writes its answers back into it as it works.

Install Source Agent contract

  1. you annotate Mark a line or a range in the commit’s diff.
  2. you hand off One keypress releases every open annotation at once.
  3. agent fix, then report Each note comes back with an answer attached.
  4. margin markers flip They land in the review you left open.
One turn of the loop. None of it leaves the repository.

1.0Annotate

Run margin inside the repository. It opens on the commits in <base>..@; navigate the diff, press v to take a range, a to write the note.

Uncommitted work is reviewable as it stands. Under jj the working copy is the @ revision; under git it leads the list as (uncommitted changes) whenever tracked files differ from HEAD. Your agent left the tree dirty, so that is where the review starts. You don’t have to commit anything first.

Annotations append to .margin/annotations.ndjson, an event log that sits in the repository beside the code it describes. Nothing is ever edited in place. Deletion and undo are logged as compensating events, which is what makes the timeline trustworthy and lets an agent write to the log while the TUI is open.

2.0Hand off

Press H. Every open annotation is released to a waiting agent. margin list --json --watch, which blocks until exactly that moment, prints the review and exits.

You tell the agent once that you are going to review something, and it waits instead of polling a file on a timer. You can also skip the second terminal: x launches a headless agent on the focused annotation and X on every open one. L streams the session below the diff.

3.0The agent replies

An outcome is a logged event, and it carries whoever recorded it along with whatever they said about it.

write side

$ margin status a3f9 resolved --reply "Hoisted the lock; the loop borrows it now."
$ margin status b71c wont-do  --reply "Deliberate. The retry needs the old error."
$ margin status b71c open     --reason "The old error is discarded two lines down."

Those replies read back out. list --json carries each annotation’s history, oldest first, so a later run already knows what was tried and why you turned it down.

4.0Re-review

margin watches the log. Markers flip in the open TUI as the agent records them, without a restart or a reload. The reply is attached to the line it answers, so there is nothing to reconcile from memory. t opens the annotation’s timeline, and r reopens one that was closed too early.

margin in the terminal: a syntax-highlighted diff with two inline annotations, one still open and one resolved by an agent with its reply.
The annotation overview above the diff. It is rendered from the real UI, so it stays in sync with it.

The fix rewrites the commit you annotated.

That is the normal case. The agent amends or rebases the very revision you were reviewing, and margin follows it there.

jj derives this exactly, from the change id that every rewrite preserves. git has no such identity, so margin reconstructs one. A commit that history no longer contains is matched against recent commits on all refs by author, author date and subject, the fields that amend, rebase and cherry-pick leave alone. Where git gives it nothing to go on, margin says which case it landed in.

revision_state The annotated commit
unchanged Still in history, exactly where you left it.
amended Rewritten, and followed. The annotation carries the new current_commit.
divergent Now in more than one place, and margin will not guess which.
abandoned Gone, with nothing left to match. A reworded git commit reads this way.

Omitted for annotations on git’s working copy, which has no commit identity to follow.

The CLI is the contract.

The agent reads the review and writes its outcomes through these commands. The store itself stays private.

the whole surface

$ margin list --json                        # the review, folded and versioned
$ margin list --json --watch                # … but block until the hand-off
$ margin status <id> resolved --reply "…"   # addressed
$ margin status <id> wont-do  --reply "…"   # declined, with a reason
$ margin status <id> open     --reason "…"  # reopened for another pass
$ margin install-skill --print >> AGENTS.md # teach any agent the above

list --json folds the event log into the current state of each annotation, including its status, its re-anchored location, the snippet and the history. install-skill writes the same document into a skills root with --dir, or to stdout with --print, for whichever file your agent actually reads.

Install

two ways

$ binge install matze/margin                 # pre-built binary onto $PATH
$ cargo install --git https://github.com/matze/margin

Binaries are on the releases page and can be dropped anywhere on $PATH; binge automates that. Building from source needs only an existing Rust toolchain.

Next to the neighbours

margin reviews the commits your agent is about to rewrite, and it keeps that review as state in the repository. The tools below solve neighbouring problems, and the table says where each of them wins.

  margin tuicr hunk lumen
Agent reports outcomes back into the review yes no no no
Annotations survive amend or rebase of the reviewed commit yes no no no
Annotations stored in the repository yes app-data dir session session
Append-only history, undo, per-annotation timeline yes no no no
Review stays open, agent updates land in it live yes no yes no
Agent launched from inside the review yes via tmux no no
Push an inline review to GitHub or GitLab no yes no no
Review someone else’s pull request no yes no GitHub
AI commit messages, explanations, command generation no no no yes
Backends git, jj git, jj, hg git, jj, sapling git, jj
Vim model, mouse, theme gallery minimal extensive mouse, themes mouse, themes

Non-goals. Pushing an inline review to a forge, reviewing someone else’s pull request, mercurial, a full vim modality, a theme gallery, a stable library API. Conceding that breadth is what keeps the loop small enough to stay correct. If you want a pull-request client, tuicr is the better tool.