Pair remembers where the last session in a project stopped, can look up what earlier ones said, and learns from you when a remark is worth making. Both live in the daemon's data directory (sessions.sqlite), never in your repository. They are stored only on your machine, but the Partner's model gets the last note and what recall finds, and a decision model your verdicts (data.md).
A session starts when a Neovim or a chat pane opens in a project; there is nothing to start. Its goal is usually written down already, so Pair takes it from there instead of asking:
- the title of the work board's (Shepherdr's) task for the branch, matched by the repo's space and the number
(
work/7-retriesin repohookdis taskHKD-7of the spacehookd, never another space's task 7); - else the words of the branch name (
fix/reconnect-after-sleepis "reconnect after sleep"); - else the chat asks: "What are we doing this session?", and the first line you type there is the goal.
later(orexit,skip,none) skips the question, and the next line is a question for Pair again. A question you ask in Neovim (:Pair ask, or aloud) is always answered, never taken for the goal./goalin the chat or:Pair goalsets one any time.
The chat says where the goal came from (Goal, from the board task HKD-7: Retries reach the budget), and the
context card shows it. The board is read with its own command
(shepherdr space list, shepherdr task list --space <space> --json, shepherdr task show <id> --json);
"board": null in the config turns that off.
Once the session has a goal, Pair proposes it as two to four steps, in one line of its own, at your next break (not
while you type, nor among :Pair new's questions):
pair Steps: 1 the cart tests pass · 2 the bogo code works
·· p edits the steps · /plan off drops them
You keep them by doing nothing. p alone on the line, then Enter, puts /plan the cart tests pass; the bogo code works on the line to edit and send (a step you keep word for word stays done); /plan a; b; c sets steps of your own,
/plan off drops them, and /plan alone shows them, done and next. A goal too small for two steps gets none, and a new
goal drops the old goal's steps. Steps you set win over a proposal Pair is still thinking about. "plan": false in
the config turns the proposals off; /plan still sets steps of your own.
When a step may have landed (a test run or a command that failed passes again, you take a suggestion or code from
/gen, you commit), Pair waits for your next break and then checks it against the same facts the
debrief is written from: the tests and commands that went green, Pair's
changes you kept, the commits and git status. Only a step those facts show done counts, and Pair says one line:
pair done: the cart tests pass; next: the bogo code works
Neovim shows the line too, under your cursor, for a moment, as it does a break hint: without the chat pane open you still hear it.
A landing that finished no step says nothing. The steps go with every question and remark as Plan (✓ done, → next, ○ open): ✓ 1 … · → 2 … (next), so "what's left?" is answered from them, and the debrief reads them too.
Each of these lines is an unasked remark: it costs the talk budget like any other, and with the
budget spent, a proposal waits unsaid until the budget allows (p still shows it): until Pair has said it, it is only
a proposal, kept out of the Plan line, the done checks and the debrief. A landing is still checked at your break and
its steps marked done at once; only the "done" line waits for the budget, for up to half an hour. The ledger
(/today) records each line said, each one held back or let go (a change of plan lets a held one go), and each
change to the steps. The plan lives in the session only: never in NOTES.md, the brief or your files, and it is
gone when the session ends.
What you just changed. Each of Pair's turns (a remark, a question, /drive, an explanation) also carries the lines
you changed since Pair's last turn, read from your buffer as they are now (up to 60 lines, the newest first), with the
words "any earlier read of them is stale". Pair's conversation holds what it read before, and without them a suggestion
could put back a bug you had just fixed.
A session ends by itself, and writes its note:
- after 45 minutes in which you neither typed, edited, moved or scrolled in Neovim, ran a command nor asked anything;
- a minute after its last Neovim and chat pane closed (a Neovim you restart at once keeps it).
A session you did no work in ends without a note, also with /end: there is nothing to note. Work is typing, an edit,
a save, a command, a search or a question; an agent's Neovim, one opened to look, or the empty session Neovim starts
right after :Pair end (moving about, closing the debrief's panel) is none, so it never writes over the day's debrief.
An edit Neovim has not sent yet (it waits for a pause in typing) goes with the session that ends: :Pair end sends
it first, and a session that ends otherwise takes it along, so the next one starts empty.
/end still ends a session at once.
/end in the chat (or :Pair end) ends the session, and so does Pair by itself. Pair then asks the Partner for the debrief, in a fixed form: one to three ✓ lines on what is now true, up to two ○ open: lines on what is left, and Tomorrow, first: <step>, in <file>:<line>. With the request it gets the facts, so even a small model writes from what happened rather than from memory:
- the goal, and the session's steps, done and open;
- the tests that failed during the session and pass now, the commands that pass again after failing, and the numbers that moved the right way;
- Pair's changes you kept (suggestions you took, edits you did not undo) and the session's commits (
git log); - the tests still failing,
git status --short(the first 20 lines), and where your cursor was, with the code on that line.
The chat shows it under a heading with the session's active time (Day's end: 3 h 05 min in hookd., or Session's end before three in the afternoon), followed by one line of Pair's own about the remarks you rated noise, when there were any (2 of my 5 remarks were noise, both about drift: I'll keep quieter there; 1 got no answer.): a remark that got no answer is counted beside them, never as noise. Its lines are the note kept with the session; its first step is kept as a place (the file, the line and its code), which the next morning's pick-up lands on, with the failing run if the session ended red. /first <step> changes that step.
A heading the model adds itself (Session note:, # Debrief, …) is dropped, also when the text follows on the same line. If the Partner cannot answer (the model is down) or answers with nothing, no note is kept (the chat says why when the model failed): the next session then starts from the last good note.
The next session in the same project (the same git root, or session root) starts from the last note, in two ways:
-
The chat shows it before the new goal (or before asking for one), unless the last session ended less than 30 minutes ago and its debrief is still on screen:
pair Last time: ✓ RECONNECT_TIMEOUT_MS in src/ws.ts is now 30000. ○ open: try it on a real phone. Tomorrow, first: test the reconnect on a phone, in src/ws.ts:42. pair What are we doing this session? Type the goal here, or "later". -
The Partner's instructions carry it, with the advice to use it when you ask about the last session or its loose ends, and not to bring it up otherwise. "What did we leave open last time?" gets an answer from it; a remark nobody asked for is not made because of it.
-
The first Neovim, at least 30 minutes after that session ended, picks up at the debrief's first step.
Each project has its own memory. Only the last note is carried over on every turn, not the whole chat or the old conversation: that keeps the context small for local models. The chat history and every note stay in sessions.sqlite, where Pair looks them up when they matter.
"What did we try last week?" reaches further back than the last note. The Partner has a recall tool, in every role: it searches the goals, notes and chat lines of the project's earlier sessions for a few words, and answers with the best matches, each with its day:
From the earlier sessions of this project, best matches first:
- Session of 2026-10-20 (goal: reconnect drops), its note:
✓ The reconnect waits 30 s now.
- 2026-10-20 14:02 (goal: reconnect drops), the user: did raising RECONNECT_TIMEOUT_MS help?
- 2026-10-20 14:03 (goal: reconnect drops), you: No: the socket closes before the timeout.
A question that is plainly about the past ("last week", "yesterday", "did we", "we tried", "do you remember") comes with that search already done, so even a small model that would not think of the tool answers from what was said then, not from a guess. Other questions do not, and no remark is made because of what an old session said. The search stays in the project: another project's sessions, and this session's own chat (the Partner has that already), are never in it. Each search is a recalled line in the ledger.
Every remark Pair makes unasked can get your verdict: useful, or noise. Answers to your own questions are not remarks and get none.
| Where | Useful | Not useful |
|---|---|---|
| Chat pane | + (or /useful) |
- (or /noise) |
| Neovim | Alt-u (<M-u>, or :Pair useful) |
Alt-n (<M-n>, or :Pair noise) |
| A suggestion in the remark | accepting it (Alt-y, :Pair accept) |
:Pair dismiss |
-
In the chat, a remark ends with its action line (chat.md): its
+ useful - noisekeys rate that remark once it is picked (inpair chat --plain, a dim·· + useful - noiseline). A+or-typed and sent goes to the last remark, also after the daemon restarted. -
An older remark is rated from a list. In the chat,
/remarkslists the last nine remarks in the project, newest first, with the first line of each and what became of it;+2(or/useful 2) rates the second last useful,-3(or/noise 3) the third last noise, and so on:·· 1 14:05 drift Edits in docs/ are off the goal. (waiting) ·· 2 10-26 09:30 failed_command The retry count is off by one. (ignored) ·· +2 or -2 rates the second last, and so onThe numbers are those of the list you saw, even when Pair made another remark since. In Neovim,
:Pair todaylists the day's remarks with the rest of the ledger, where+and-rate the one under the cursor. -
In Neovim,
Alt-uandAlt-nare mapped (in Normal mode) only while a remark waits for a verdict, and only if you have no mapping of your own on them; they go away after the verdict. Change them with the plugin'suseful_keyandnoise_keyoptions, orfalsefor none; the commands work either way. -
A later verdict replaces an earlier one:
-, then accepting the suggestion after all, counts as useful. -
A remark you act on counts as useful. When you edit the lines a remark pointed at, give or take two lines, within ten minutes of it showing, the remark counts as useful, wherever its words reached you: under your line, in the chat, or nowhere because you were in your terminal. Only your own edits count, not Pair's, and not on lines you were still editing when it showed (in the last two minutes, with no command run since: a line you changed and then ran the tests on does count), nor an edit of more than 20 lines at once (a format of the whole file). Editing a suggestion's lines ignores the suggestion, as before; taking it is the verdict. The ledger books it as a
counted usefulline, and the chat marks the remark✓ useful. Your own verdict always wins: a remark you rated noise stays noise, and-afterwards still makes it noise. -
A remark you see and work on elsewhere is neither. In Neovim, Pair's words go with your next edit, and that edit (or
Alt-jto where it points) counts the remark as seen: neither useful nor noise, and it does not move the tuning. A verdict, or an edit where it pointed, still replaces it. -
A remark you never answer counts as not useful for the tuning. With no verdict after ten minutes, not seen and not acted on, a remark is counted as ignored. Before that it is "waiting" and counts for nothing. The day's end debrief does not call ignored remarks noise: only the ones you rated noise.
-
A remark that ends in SILENT never reached you, so it is not stored and cannot count as ignored.
With "autoTune": true (the default), Pair tunes two things from your verdicts, and on a slow server a third: which kinds of moment it stops asking the Watcher about first (watcher.md). It looks at the verdicts of the last 30 days, at most the last 20 per trigger, so it follows your recent habits.
Per project. A project where drift remarks help and one where they are noise each get their own bar. A trigger's bar follows the project's own verdicts on it once there are enough of them (the minimum below), and the verdicts of all projects until then; the same goes for the budget. A new project starts from all your verdicts, and moves away from them as it gets its own. pair stats --project says which parts are the project's own.
Each trigger's bar (the Watcher score a remark needs), once a trigger has at least 5 verdicts:
| Kept (useful ÷ judged) | Bar |
|---|---|
| under 25% | +2 |
| 25% to under 40% | +1 |
| 40% to 75% | as the level says |
| over 75% | −1 |
The budget (remarks per hour, and the gap between them), once there are at least 10 verdicts in all:
| Kept, over all triggers | Remarks per hour | Gap |
|---|---|---|
| under 30% | half | twice as long |
| 30% to under 50% | three quarters | a third longer |
| 50% to 80% | as the level says | as the level says |
| over 80% | one and a half times | two thirds |
The safe bounds hold whatever the numbers say: a bar moves at most 1 down and 2 up from the level's, and stays between 3 and 9, so no trigger is ever silenced completely; the budget scales between half and one and a half times the level's, and always allows at least one remark per hour. The talk level stays yours: /level moves all bars and the budget, and the tuning applies on top.
"autoTune": false in the config keeps the level's bars and budget, and a slow server gives kinds up in its fixed order. Your verdicts are still recorded, and pair stats still shows them.
pair stats shows the last 7 days (--days N for another span), across all projects; pair stats --project shows the project of the folder you are in (or pair stats --project DIR, of that folder), with its own bars and budget. It reads the data directory, so it works whether the daemon runs or not.
Last 7 days, all projects: 6.5 h of active pairing, 15 unasked remarks (2.3 per hour)
useful 7 · dismissed 3 · ignored 4 · waiting 1 · kept 50%
trigger remarks per hour useful dismissed ignored kept bar
failed_command 5 0.8 4 0 1 80% 5 (-1)
repeated_error 2 0.3 1 1 0 50% 6
new_code_diagnostic 1 0.2 1 0 0 100% 6
stall 1 0.2 0 0 0 - 6
drift 5 0.8 0 2 3 0% 8 (+2)
edit_while_red 1 0.2 1 0 0 100% 6
Budget at level normal: at most 5 remarks per hour, 4 min apart (the level's default: 6, 3 min)
Tuning follows your verdicts of the last 30 days: a trigger needs 5 of them to move its bar, the budget 10. Each project tunes on its own verdicts once it has as many (pair stats --project).
-
Active pairing is the time in which you typed, edited, ran a command, moved the cursor or scrolled in the last five minutes, while a session was open: reading code counts. A cursor Neovim reports again where it already was does not, nor a split Pair opens. Reading alone is no work for a debrief, though: a session you only read in ends without one. It is stored per clock hour, when it happened, so a session left open for days counts toward each day you used it, and
--days 1shows today's work even in a session started last week. Remarks per hour are counted against it, not against the hours a session stood open, and shown from six minutes of it on. With--project, it is the time in that project (counted per project since v0.9). -
kept is useful ÷ (useful + dismissed + ignored); remarks still waiting, and seen ones, do not count. The first line also counts the seen remarks, when there are any.
-
bar is the Watcher score each trigger needs now, with the tuning's change from the level's bar in brackets.
-
Budget is what the tuned budget allows now at your configured level.
-
Decision model: with a decision model in the config, a last section says what it sorted the moments as, how many Watcher calls that saved, and how its remarks fared next to the rules' remarks, which is how you tell whether it does better than the bar (decision.md).
-
/gen: with /gen requests in the window, a last section counts them per shape, the pieces taken and dropped, and the lines taken by what became of them: made yours (edited, or
:Pair own), with their share, not yours yet, and gone (gen.md). Code taken and never owned is the number to watch:/gen: 6 requests (here 3 · file 2 · scaffold 1); pieces taken 5 · dropped 2 240 lines taken: yours 120 (50%) · not yours yet 100 · gone 20