Agent workflows: the rituals
This document is written for a coding agent connected to a logbook project over
MCP. It is a set of directives you can follow verbatim. It assumes the domain
model in docs/guide/concepts.md — read that first if a term
(log, epic, size, resolution, item_files) is unfamiliar.
The organizing principle behind every ritual here is context economy: MCP tool results stay resident in your session context for the rest of your lifetime, so the cost of a survey is paid for the whole session, not just once. Index rows are cheap; item bodies are expensive. Every ritual below is shaped to keep bodies out of context until you are actually going to work an item.
Session start: the briefing
At the start of a session, call briefing once. It returns three things in
a single call — your unread inbox (and marks it read), the items assigned to you
with task progress, and the team scoreboard — so you do not need separate
check_inbox / get_my_work / team_summary calls.
briefing
Read the inbox first: new assignments, notes left on your items, and status
changes are how work and context reach you. If you only need notifications, use
check_inbox (also marks read); if you only need your queue, use get_my_work.
Surveying a backlog cheaply
Do not survey by listing full items. Follow this escalation, cheapest first:
-
backlog_statsFIRST. For any survey or audit, call this before anything else. It returns the shape of the whole backlog in one small object with no item bodies: total, counts by status, counts by size (plus how many areunsizedand need cleanup), counts by kind (log vs epic), top labels, per-assignee open counts, and the oldest in-progress item. Understand the shape here first. -
list_backlog, filtered, atindexverbosity. Then — and only for the slice you care about — list.list_backlogreturns an index (one line per item: ref, title, summary, size, status, priority, assignee names, task progress, labels) with no descriptions or bodies. Filter it: bystatus,assignee_email,label,size(array, OR semantics),kind,epic(an epic's children), or free-textsearch. It defaults to a limit of 50 and excludes done items; page withlimit/offset. -
Pull only what you will touch. Use
pull_item/pull_items(below) to read bodies for the specific items you are about to work — never as a survey.
Never run a full-verbosity survey. list_backlog accepts
verbosity: "refs" | "index" | "full", default index. refs is the leanest
({ref, title, size, status} only — for very large orgs or pure lookups).
full returns complete item bodies and exists only as a compatibility escape
hatch for exceptional cases; its own description says it is "NOT for surveys."
Do not reach for it to look around.
Picking up work
To pick up a single item, use pull_item — it gets everything in one call:
problem, description, task checklist, notes, links with live PR state,
assignees, recent activity, related items, and (for an epic) its children as
index rows. Find it by ref (LOG-12, 12) or title text.
Pass claim: true to assign yourself and move the item from backlog to
in_progress in the same call:
pull_item { query: "LOG-12", claim: true }
Claiming is idempotent — it only writes if you are not already an assignee or
the status is still backlog. Do not claim an item you are only inspecting.
Batch sessions
When you are working a cluster — building out several items in one session, bulk-closing, bulk-relabeling — use the batch tools instead of dozens of single-item calls.
pull_items— fetch many items completely in one call. Select by explicit refs/queries (["LOG-12", "LOG-88"]) or by filter (status/label/assignee_email/size/kind/epic/search+limit). It returns the fullpull_itempayload per item (per-item recent activity is omitted —pull_itemone ref if you need it). Passclaim: trueto claim every selected backlog item at once. Pulling an epic's work queue is a filter:pull_items { epic: "LOG-12", size: ["S"] }.create_items— file many structured items in one call (see Planning).update_items— apply updates to many items in one call. Unlikecreate_items, updates are processed independently — one failure does not stop the rest, and the result reports ok/error per item. When bulk-closing, include a per-itemresolutionon every entry.
Planning: filing a well-formed plan
Use the plan-work prompt for anything non-trivial
logbook ships a planning interview as an MCP prompt. In Claude Code it appears
as the slash command /mcp__logbook__plan-work (optionally seeded with a
one-line idea). Use it whenever you are turning a rough idea into backlog
items — it is the "spend time planning" surface, and it keeps a human in the
loop by design: it files nothing until they approve. The prompt walks you
through:
- Interview the human a few questions at a time, PM-style: what problem and for whom, what "done" looks like, what is explicitly out of scope, constraints and deadlines, and how the work might split.
- Check prior art before proposing —
backlog_stats, filteredlist_backlog+search, andhistory_for_fileson any repo paths they name — and report findings in plain terms ("we did something similar in LOG-42") so you build on existing work. Calllist_labelsin the same pass and adopt that taxonomy; do not invent labels. - Apply the decision test (see
concepts.md) and state the verdict: one PR closes it → a single log; independently shippable pieces → an epic with sized children. - Draft the full plan for approval as text — epic summary line; each
item/child with title, one-line summary, size (treat
XLas a signal to make it its own epic), labels from the existing taxonomy, and an initial task checklist. Fold acceptance criteria into descriptions; flag any long-form spec as a document to attach after the items exist. - On approval, file it in one
create_itemscall and attach any flagged specs, then report the created refs.
Filing an epic + children by hand
When you are not running the prompt, file the whole plan in one create_items
call so it lands together. Put the epic entry first, then link each child with
epic_index — the 0-based index of the epic entry in the same batch:
create_items { items: [
{ kind: "epic", title: "Checkout revamp",
summary: "Rebuild checkout as independently shippable steps" },
{ title: "Cart summary panel", size: "M", epic_index: 0,
tasks: ["wire totals", "empty state"] },
{ title: "Payment step", size: "M", epic_index: 0 },
{ title: "Confirmation page", size: "S", epic_index: 0 }
] }
Rules to file cleanly:
- The epic entry carries no
size(containers are not sized) and a strongsummary(it is the container's one-liner). A size on an epic is an error. - Every log entry carries a required
size(S/M/L/XL). - A child links its parent by
epic_index(an epic in this batch) ORepic(an existing epic ref like"LOG-12") — never both. - Labels must already exist. Call
list_labelsfirst;create_labelfor any genuinely new one, reusing over near-duplicates. - Validation is all-or-nothing: an unknown label/email, a bad epic ref, an
epic pointing at a non-epic, or an out-of-range/non-epic
epic_indexaborts the whole batch before anything is created — fix and retry safely.
For a single item, create_item takes the same fields. Always state the
problem; a log always needs a size.
Enrichment: raising item quality
Use the enrich-log prompt
To raise the quality of an existing item, use the /mcp__logbook__enrich-log
slash command (argument ref, required). It audits one item against the quality
bar and, like plan-work, changes nothing until the human approves. It:
- Pulls the item and reads the whole thing.
- Audits against the quality bar — a missing size, a missing one-line summary, no problem statement, no acceptance criteria, an XL log that should be an epic, a description that is really several separable deliverables, or a stale status — and notes only the gaps actually present.
- Asks the human only about those gaps (not a fixed questionnaire).
- Applies the fixes in a single
update_itemcall if the item stays whole. - Proposes a split rather than forcing it if the item is really several
deliverables — new epic + children via
create_items, re-parenting the original or closing it as superseded — the human's choice.
Backfill opportunistically
The backlog carries legacy items with no size and no summary. Do not run a
mass-cleanup pass, but whenever you have an item pulled anyway, backfill it:
update_item accepts size (to size an unsized legacy item) and summary
while you are there. backlog_stats.unsized is where unsized items surface —
they never match a size filter, so sizing them is what puts them back into
shape queries.
Working an item
The completion sequence matters. Follow it exactly.
- Claim it.
pull_item { query: "LOG-12", claim: true }(orpull_itemswithclaim: truefor a cluster). You are now an assignee and it isin_progress. - Work the task checklist. As you complete each step, check it off with
complete_task(identify the task by exact title or a unique substring). Add steps you discover withadd_task. Remember tasks are steps of this one deliverable — if a "task" is really a separate shippable piece, it belongs as an epic child, not a checkbox. - Attach your branch and PR as you go. Use
update_itemwithadd_linksto attach abranchlink when you start and aprlink when you open the pull request:update_item { number: 12, add_links: [{ kind: "pr", value: "<pr-url>" }] }. The PR URL is what the merge webhook matches on, so this link is what wires the item to its automatic cross-off. - Do NOT mark the item
donewhen you open the PR. Leave the statusin_progress. When the PR merges, the GitHub webhook crosses the item off automatically — it setsstatus = done, records the changed files intoitem_files, and leaves a merge note. Marking itdoneyourself at PR time is premature: the work is not shipped until it merges, and doing it by hand both races the webhook and skips the file-history capture. Let the merge be the thing that closes it.
The one exception: if there is genuinely no PR (a docs-only change, a decision
recorded, work abandoned), close it yourself with update_item and a
resolution — but that is the exception, not the flow.
Completing: the resolution quality bar
Whether the webhook closes an item or you do, the item must carry a real
resolution. When you close something by hand, update_item requires it:
"When marking done, ALWAYS write a resolution."
The bar: write the changelog entry you would want to find in six months. Be
concrete — what changed, where, and which decisions were made. "Fixed it" is not
a resolution. This text is not bookkeeping: it is what a future agent reads when
it queries the history of a file, and what history_for_files returns as the
resolution_excerpt. Resolutions accumulate into the de-facto documentation of
what the project built (see concepts.md), so the quality of that documentation
is exactly the quality of the resolutions you write.
The webhook's auto-stub (Resolved by PR #N … plus a file summary) is a
fallback for items nobody wrote a resolution for — it never overwrites a
hand-written one. Prefer to write your own; the stub is not the changelog entry
you would want in six months.
Before touching unfamiliar code
Before you modify a file you did not just write, run the history_for_files
ritual. Its description states it plainly: "Before modifying a file, ask what
happened to it before — returns the items and resolutions that shaped these
paths."
history_for_files { paths: ["lib/db/schema.ts", "app/api/mcp/"] }
Pass one or more repo-relative paths (1–20). A trailing slash makes a path a
directory query (everything under it); otherwise the match is exact and
case-sensitive. Each path returns the matching items newest-first, each with a
resolution_excerpt and the PRs that touched the path. git log tells you what
changed; only logbook holds the intent and the resolution behind it, which is
what stops you from re-litigating a decision that was already made and written
down.
Context economy rules
Internalize these so the rituals above make sense:
- Index rows are cheap; bodies are expensive. An index row is roughly
40–60 tokens; a full item is 250–600+. An unfiltered 200-item survey is ~10k
tokens at
index, ~4k atrefs, and ~80–120k atfull.backlog_stats(~150 tokens) replaces most surveys entirely. - Everything you pull stays in context. MCP results are resident for the session's whole lifetime. Pulling bodies you will not act on is a cost you pay for the rest of the session.
- What each verbosity level is for.
refs— pure lookups and very large orgs.index— the default; scanning and selecting work.full— a compatibility escape hatch for exceptional cases, never a survey. Usepull_item/pull_items(notlist_backlog { verbosity: "full" }) when you actually need bodies, because those are the "I am working this item" paths and are meant to be complete. - Documents and changed-files stay out of the way. Attached documents show
only their filename until
get_document; index rows never carrychanged_files. Fetch long-form content only when you are going to read it. - For a very large audit, delegate the reads. Spawn a cheap subagent to do the reading and return a distillate, and keep your main session's context to index rows and conclusions.