LLM task journal & shared inbox manager
The reins of a harness β steer an LLM with local Markdown task docs,
safe CLI mutations it can't corrupt, and GitHub only when you need to share.
- π Markdown is the source of truth. Task docs live in a local store; GitHub is a publishing and review surface, not the truth.
- π The LLM can't corrupt the doc. State changes go through CLI mutation commands (
check/log/fail), never direct Markdown edits. - π³ Parallel-safe. Each git worktree binds to its own task, so multi-agent runs never cross-talk.
- π GitHub when you want it. Publish issues, attach PRs, and sync a marker-wrapped managed section β human text outside the markers is preserved.
- π₯οΈ At a glance. A cross-project TUI (
rein ui) and a Neovim plugin to toggle it in a float.
See PLAN.md for the design rationale and decisions.
cargo install reins # the installed command is `rein`Or from source:
cargo install --path .The crate is published as reins (the singular rein was taken); the command you run is rein. Requires git. GitHub integration (issue/pull/push, etc.) requires the gh CLI.
- store: a per-repo local store (
~/.local/share/rein/<key>/). The key is a UUID thatrein initwrites togit config rein.store, so the store is immune to worktree and directory moves, and living outside the repo means task docs are never committed by accident. Override the location withREIN_ROOT. - state = directory:
inbox/βactive/βdone/YYYY-MM/, pluscanceled/. Thestatusin frontmatter is derived. - item IDs: the
<!-- task:N -->on a checklist item is a stable integer assigned once and kept (not a line number). The tool assigns it whenever it touches the document, andrein check <N>uses that number. - task resolution order:
--task <id>β worktree pointer βREIN_TASKβ the store'scurrentfile.
rein new "settings cleanup" # create a draft in inbox (prints id + path)
rein open settings-cleanup # write Goal/Tasks/Validation in $EDITOR
rein start settings-cleanup # inbox β active, sets current to this taskThen hand it to Claude Code; following the skill rules, the LLM proceeds:
rein todo # list remaining unchecked items (skill entry point)
rein check <item-id> # check off a completed item
rein log "implementation note"
rein fail <item-id> --reason "β¦" # resolve an item as failed (drops out of todo)
rein retry <item-id> # reopen a failed itemWhen done:
rein done # active β done/YYYY-MM/ (clears current)You don't have to add <!-- task:... --> IDs by hand β the tool assigns them (check needs an ID).
rein start feat-a --worktree # creates ../proj-wt/feat-a + branch rein/feat-a
rein start feat-b --worktreeEach worktree is bound to its own task, so an agent just runs commands from its own cwd:
cd ../proj-wt/feat-a && rein current # β feat-a (resolved from cwd)
cd ../proj-wt/feat-b && rein check x # β edits only feat-b, no cross-talkClean up explicitly from the parent session (rein done feat-a / rein cancel feat-b --force). Running a mutation without a task in the main repo is blocked by a guard when two or more tasks are active β pass --task or run it from the right worktree.
rein issue settings-cleanup # publish a GitHub issue (rein label, marker-wrapped)
rein pull-inbox # import rein-labeled issues (idempotent)
rein pull # apply remote issue-body changes
rein push # push local changes into the issue/PR managed sectionOnly the managed section between the rein:begin/rein:end markers is updated on the remote body; human text outside the markers is preserved. Conflicts are detected by a 3-way hash, backed up under conflicts/, and force-pushed with rein push --resolved after you resolve them. Attach a PR with rein start β¦ --draft-pr or rein attach-pr <n>, then update it with rein push (the Agent Log folds into a <details>).
A single dashboard across all your projects. Launched inside a repo, it pre-scopes to that project; press P to pick another.
| key | action |
|---|---|
j/k |
move |
Tab |
cycle status (all/inbox/active/done/canceled) |
P |
pick project (project > task hierarchy) |
Enter |
edit in $EDITOR |
n |
new task |
s |
start (inbox β active) |
m |
move to any state (i/a/d/c) |
d |
done |
p |
publish issue or push |
/ |
filter (matches project name too) |
q |
quit |
Editing is always delegated to $EDITOR β there is no built-in Markdown editor in the TUI.
Failed items (resolved via rein fail) render in red and struck through in the preview, distinct from green done and yellow open.
The repo doubles as a Neovim plugin that toggles rein ui in a floating terminal β install it like any other plugin.
Prerequisites: the rein binary on $PATH (see Install) and Neovim 0.10+.
With lazy.nvim, add the spec:
{
"devgony/rein",
cmd = "Rein",
keys = { "<M-r>" }, -- lazy-load trigger; the plugin owns the mapping
opts = { keymap = "<M-r>" }, -- Alt-r toggles the float both ways
}On LazyVim, put that in its own file, ~/.config/nvim/lua/plugins/rein.lua:
return {
{
"devgony/rein",
cmd = "Rein",
keys = { "<M-r>" },
opts = { keymap = "<M-r>" },
},
}Hacking on rein itself? Point at the working tree instead of GitHub, and set dev = true so each toggle launches the TUI from source via cargo run (a fast incremental debug build) β your edits show up on the next toggle with no cargo install:
{ dir = "/path/to/rein", name = "rein", cmd = "Rein", keys = { "<M-r>" }, opts = { keymap = "<M-r>", dev = true } }dev = true auto-detects the repo from the plugin's own location; pass a path (dev = "/path/to/rein") to point elsewhere. :lua =require("rein").command() prints exactly what will run.
Usage: <M-r> (or :Rein) opens the dashboard centered as a 95% Γ 95% float and closes it again from inside the TUI β one key, both ways. You can also quit the TUI with its own q. Failed items show in red (struck through). Set keymap = false to skip the built-in mapping and wire your own key to :Rein (give it mode = { "n", "t" } so it toggles out from terminal mode too).
Options (opts = { ... }, defaults shown):
| option | default | meaning |
|---|---|---|
cmd |
"rein ui" |
command to launch (string or argv list) |
dev |
false |
true (auto-detect repo) or a repo path β run from source via cargo run instead of the installed binary |
width / height |
0.95 |
<= 1 fraction of the editor, > 1 absolute cells |
border |
"rounded" |
any nvim_open_win() border style |
keymap |
"<M-r>" |
toggles in normal mode and closes from inside the TUI; false to skip |
rein init [--skill] create the store + register git config rein.store (--skill: scaffold SKILL.md)
rein new <title> [--shared] create a task draft in inbox
rein list [--status <s>] list tasks
rein todo [--all] [--task <id>] resolved task's unchecked items (--all: all items + state)
rein open [task] open in $EDITOR (fuzzy picker with no argument)
rein current [--path] print the resolved task (read-only)
rein use <task> switch the task binding (worktree pointer / current file)
rein move <task> <status> move to any state (plain relocation, no side effects)
rein start <task> [--worktree] [--branch <b>] [--draft-pr]
rein check / uncheck <item-id> [--task <id>]
rein log <text> [--task <id>]
rein fail <item-id> --reason <text> [--task <id>] resolve as failed (checked + struck through, drops from todo)
rein retry <item-id> [--task <id>] reopen a failed item
rein issue <task> | pull-inbox | pull | push [--resolved]
rein attach-issue <n> | attach-pr <n>
rein done [task] [--keep-worktree]
rein cancel [task] [--keep-worktree] [--force]
rein doctor rebuild state/, fix frontmatter drift
rein status | root | ui
rein init --skill # scaffold .claude/skills/run-rein-task/SKILL.mdThe skill gets remaining items via rein todo and changes state only through rein check/log/fail (never editing the Markdown directly). The full rules live in the scaffolded SKILL.md.
