Generated from
cat-harness/skills/sdlc/sdlc-core/todo-manager.md— do not edit here.
This is the skill
skill_fetchserves. A stub of the same name lives at.claude/skills/localand is published as todo-manager (local stub); it only points here. Edit this page’s source, never the stub.
Session Task Manager (beans)
Disambiguation:
beans(todo-manager, this file) = the agent’s session work-plan. Managed entirely via thebeansCLI issue tracker (data stored inbeans/).sidecars(*.qa.jsonand*.witness.jsonfiles) = content state tracking. Beans and sidecars are NOT synonymous! Do NOT convert bulk QA queue items into beans. They are completely separate workflow systems.todo-review= triage of content feedback stored underfeedback/<paper>/and surfaced via the MCP/todosdashboard. That is paper-content scope, not session scope.
Instead of an in-memory list or markdown checklists, we manage session work and cross-agent coordination using the beans CLI issue tracker.
This skill was split on 2026-09-19 (bean
tdmg). It had reached 396 lines againstskill-not-a-document’s 400-line threshold while carrying three separable disciplines. What stayed here is the bean mechanics: installing the CLI, the STRICT check beforebeans create, the fallback when the CLI is absent, the status vocabulary, and the coordination rules below. What moved:
opening-brief.md— what you say before starting a bean or a topic.turn-reporting.md— what you say during and after each turn, including the STRICT “next”-line rule.No rule changed in the split. If you are looking for a report format, it is in one of those two.
Installing beans (fresh sandbox / cloud container)
beans is the hmans/beans Go binary — a
flat-file issue tracker storing issues as markdown under beans/. Cloud
sandboxes do not ship it, so reinstall on demand (Go ships in the sandbox):
cat-harness-tools/scripts/install-beans.sh # idempotent; installs into a PATH dir
# equivalently, the one-liner it runs:
GOBIN="$HOME/.local/bin" go install github.com/hmans/beans@latest
Note: the npm package named beans is an unrelated abandoned tool — do not npm install beans. Verify with beans list && beans check.
What the session-start sweep emits, and why in that order
A session-start hook runs the sweep where a hook runs at all. It installs the CLI when it is genuinely missing — bounded and quiet, falling through to the degraded reader rather than failing the hook — and prepends the install directory when the binary is already there, so a second session does not reinstall. If the sweep still reports the CLI missing, it could not be installed there.
That the installer runs, rather than being recommended, is the lesson of the 2026-09-18 incident above. An imperative somebody has to act on is weaker than the act itself; a parenthetical “run the installer for full priming” was read and not acted on.
The order is not arbitrary:
- Interaction preferences — first, because they change the form of every question that follows. A question asked before them may have to be asked again.
- Work-plan priming and the open list.
- The roadmap. A flat list of a hundred ids in creation order is data, not a plan; the milestone/epic structure is what lets an end-of-turn report say what is next and why.
- The commands the person can run themselves, the interactive browser first. An agent cannot drive a TUI on somebody’s behalf, so the only useful thing to do with one is print it where they will see it — with a copy-pasteable line whose path and branch are computed, never written in.
- Default-branch delta, sibling branch activity, CI health.
Heavy triage of new commits belongs in a background subagent, not the foreground. The sweep’s job is to tell you what changed, not to spend your first minutes reading it.
Core Directives for Sessions
- A session is a LOG, never an epic or a milestone. Do not create a
Session: …bean. What one sitting did is recorded in two places that already exist and are already read: the PR body (## Intent/## Session results,session-intent) and a bean note on each bean you worked —bun run cat beans:note <id> --title "…", written to the declarednotesdirectory ofbeans/beans.jsonand keyed by branch, so it is one file per session per bean by construction (bean-coordination§”Adding to a bean”). See §”A session is a log, not a parent” below for why, and what it cost. - Every todo is a bean, parented by SUBJECT. Each task, probe or action
item is a bean (
--type task) under the epic whose subject it is — §”WHICH parent” — not under anything representing the session.beans create "<Task Title>" --type task --parent <subject-epic-id> - No manual
.mdchecklists: Never usesession-beans.mdor raw Markdown- [ ]checklists to track global tasks. Always use thebeansCLI to prevent namespace pollution and maintain the official project tracking. - Check before you create:
beans createis not idempotent. Run the existence check below before everybeans create— no exceptions. - Brief before you work: claiming a bean records which item is taken; the opening brief records what it is taken for. Write it before the first tool call, in the chat. See §”Opening brief” below.
- Push beans to the state branch, not code PRs:
beans/is mounted from the dedicated state branch (e.g.cat/cat-harness/beans). Bean creation, claim, note, and status changes are pushed to the branch store viabun cat-harness-tools/scripts/branch-store.ts push --id beans(orbun run cat state:push). Do not commit bean files directly to code PR branches onmain.
Where beans live — the state mount and pushing changes
Under the state branch architecture (proposal cat-harness/docs/proposals/state-branch-2026-10-02.md),
the work plan does not live on main. It lives on a dedicated tip-keyed branch
store (cat/cat-harness/beans), mounted at beans/ (e.g. by bun run cat state:mount
during session start).
- The CLI still works off disk:
beansreads and writes files under the mountedbeans/defs/. - Pushing changes: When you create, update, or resolve a bean in
beans/, push the state branch:bun cat-harness-tools/scripts/branch-store.ts push --id beans # or equivalently: bun run cat state:push - Never bundle beans in code PR commits: Committing bean files directly to
code PRs on
mainis replaced by pushing the state branch. Code PRs touch code and documentation onmain; bean updates are pushed to the state branch store, keeping the work plan globally up-to-date in real time without merge conflicts or spurious CI runs.
Check before you create — beans create is not idempotent (STRICT)
beans create mints a fresh random ID on every call and dedupes on
nothing. Re-running a work-plan step is therefore not a no-op — it creates a
second bean. Before every beans create, check whether the bean exists:
T="Prove Foo.bar"
beans list --json --search "title:\"$T\"" | python3 -c '
import json, sys
t = sys.argv[1]
m = [b for b in json.load(sys.stdin) if b["title"] == t]
print(f"{len(m)} exact match(es)")
[print(" ", b["id"], b["status"]) for b in m]' "$T"
≥ 1 match → do not create. Claim the existing bean instead, with
bun run cat beans:claim <id>— which reads the default branch first, refuses a bean a sibling holds or one already closed, and records a holder note so the next session’s check can see you.Not
beans update <id> --status in-progress. That is the older advice and it is what this line used to say. It writes no holder note, so the claim is invisible tobeans:claim’s ownalready-claimedcheck — measured 2026-09-25, 97 of the 100 non-epicin-progressbeans onmainrecord no holder, and the guard answered “go ahead” for all 97 (beanc3d7). Two sessions claimed one bean 61 s apart on 2026-09-19 and shipped two PRs; that is the failure the tool exists to stop, and routing around it puts it back.Read the outcome — three of them are refusals, and
held-unknownmeans it could not tell a live sibling from an abandoned claim:bean-coordination.md§”bun run cat beans:claim”.0 matches → run the semantic check before creating:
bun run cat lsi:near "$T" # add --body "<first paragraph>" when you have oneThe exact-title check above catches the same WORDS; this catches the same work in other words. It folds the title into an LSI index of every bean (all statuses — a completed one is often the answer) and prints the nearest five plus the closest epic. Read every hit marked
←(cosine ≥ 0.7) before creating. If one is the same work, claim or reopen it rather than create; if it is related, create and say so in the body. Measured when it landed: a reworded title for the known duplicate pair3ozg/rmcffound both at 0.72–0.75. The score proposes; whether a hit IS the same work is your call (methodlsi, refusal 3).A bean ABOUT ONE OBJECT is keyed on the object, not the title. When the work targets a single named thing (a Lean declaration, a block label, a file), titles drift: “Prove Foo.bar”, “PROVE(lean): Foo.bar”, “close sorry in Foo.bar”. Neither check above is guaranteed to pair them. Measured in
qou2026-10-04: most of its 141 duplicate groups were generator re-runs filing one Lean declaration under a new title (beanyr93). So write the key into the bean as a line of its own,Target: lean:Foo.bar(orblock:<label>,file:<path>), and before creating search the WHOLE store for that line, archive included, because a completed bean is often the answer:grep -rlF "Target: lean:Foo.bar" beans/defs/ # the archive is beans/defs/archive/A hit is the same work: claim it, reopen it with a note, or link to it. A generator that files beans MUST write the key and run this check, since a re-run is exactly the case the title checks miss. The typed field this line stands in for (
targets:validated against the content graph) is beanf227.Still new →
beans create "$T" --type task, under the epiclsi:nearprinted if it fits (§”WHICH parent — the criterion nobody wrote down” below).
--search is a fuzzy Bleve query, so the exact-title comparison inside the
pipe is load-bearing — do not drop it and trust --search alone.
Why this is STRICT. In the qou folio on 2026-08-04, one agent context
re-ran its 15-bean session work-plan ~980 times back-to-back (median 19 s per
cycle, ~16 h wall). Because create is unconditional, that produced 14,688
duplicate beans — 92 % of every open bean in that repo. The damage was not
just clutter:
- the session-start sweep and the idle-backlog policy were reading mostly noise;
- ~980 copies of an already-proved item sat at
todo; - the duplicates collided with the IDs of 15 real beans, making
beans update <id>ambiguous for those; - they corrupted a later agent’s own corpus-grep — 4,928
beansfiles matched one search term across just 36 titles, inflating 14 real source files into an apparent 54 and nearly landing a false correction in a PR body.
The runaway loop is not something a doc can prevent; an unguarded create is.
This rule is platform-level so every folio inherits it.
Check before you UPDATE — --body-file REPLACES the body (STRICT)
beans create is not idempotent; beans update is not additive. They are
different failures and both cost a durable artefact, so they sit together.
A bean’s body is an append-only record of who found what, when. Every note
in it is dated and signed, and later notes cite earlier ones — the closing note
on gjli opens “the 2026-09-19 note above did”. Replace the body and you
have not edited a field; you have deleted findings that other beans, commits
and issues point at.
beans update offers four ways to write one, and only one of them appends:
| flag | what it does |
|---|---|
--body-append <text> | --body-append - | appends. - reads stdin |
--body-file <path> | REPLACES the whole body with the file |
--body <text> | --body - | REPLACES |
--body-replace-old/-new | replaces one substring; leaves the rest |
Measured 2026-09-22 on a scratch store: a bean carrying two notes, given
--body-file with a third, kept the third alone. Exit 0, no warning, no
diff, no prompt. Nothing in the output distinguishes it from an append.
For a long, multi-paragraph note, use --body-append -:
beans update <id> --status completed --body-append - <<'NOTE'
_2026-09-22T06:51:22Z_ — what you found, and how you know it.
NOTE
That is the form the long-note case actually needs, and naming only
--body-append "Your note" is why this skill did not prevent the loss it is
now recording: an agent with four paragraphs to write reaches past an inline
string argument for the flag that takes a file, and --body-file is
sitting right there, one letter’s difference from --body-append, doing
something else entirely. (beans create rejects --body-file - with a
heredoc — bean 5wrg found that separately. So the two subcommands disagree
about the same flag name, which is one more reason not to reach for it.)
The timestamp prefix is an authored convention, not a CLI feature — no flag adds it. Write it yourself or the note joins the previous paragraph undated.
Recovery, if it has already happened. The store is committed, which is the
whole reason this is survivable: git show HEAD:<bean-file> returns the body
as of the last commit, and the lost notes can be reassembled with the new front
matter. Recover from git, not from your own scrollback — a transcript is a
copy you made, and the repository is the artefact. Then say plainly what was
lost and restored; a silent repair leaves the next reader unable to tell a
reconstruction from an original.
WHICH parent — the criterion nobody wrote down
A bean’s parent is the epic whose SUBJECT it is, not the epic you happen to
be working in. That sentence was missing from this skill until 2026-09-20,
and its absence is measurable: in one session six new beans were all filed
under yj32, the epic that session was working in, while their subjects
belonged to four different epics. The owner spotted it — “beans misfiled…
wrong skills guidance? tools guidance?” — and the answer was yes, this file.
Nothing catches a wrong parent. check-bean-parents tests exactly two
things: that an open bean HAS a parent, and that the parent NAMES A BEAN
THAT EXISTS. A bean filed under the wrong epic satisfies both, so it is
textually clean, the guard is green, and it is only findable by a person
reading the roadmap. The check cannot distinguish a right parent from a
wrong one, so the criterion has to live here or nowhere.
How to choose
beans listand read the EPICS first — there are many, each with a thematic scope in its title. Do this BEFOREbeans create, at the same time as §”Check before you create”; both are questions about where a bean belongs, and both are cheaper before the file exists.- Ask what the bean is ABOUT, not what you were doing when you wrote it. A visualiser whose blocker is an unrecorded ingest relation is an ingest bean. A sticky’s art is a rendering bean. The topic that makes the work hard is usually the right epic.
- When two epics both fit, pick the one whose OTHER children you would want read alongside it, and say in the body why the other was not chosen — a parent is a claim about where somebody should go looking.
A session is a log, not a parent
Until 2026-10-04 Core Directive 1 read “Every session is a Bean … you MUST
create a parent bean (--type milestone or --epic)”, with
beans create "Session: <Branch/Goal>" --type milestone, and this section
said the opposite — file by subject — and called the conflict “real”. Agents
followed the directive, because a numbered MUST outranks a paragraph that
disagrees with it three screens down. Measured in the qou folio
2026-10-04: 292 of its 353 type: epic beans were session logs (bean
8unf, issue #2106). The roadmap’s top level — the thing beans roadmap
exists to show — was mostly a diary.
What that costs is not clutter alone:
- An epic is a claim about where to look for a SUBJECT. A session epic points at one sitting, which ended; nobody looks there for the work, so its children are lost to the next agent filing by subject.
- It defeats the guards.
check-bean-parentspasses a task parented to a session epic, because the parent exists and is an epic. The wrong parent is textually clean. - Its status lies. A session epic stays
in-progressafter its session ends, so it is a stale claim from birth, and the quiet-claim findings fill with logs.
Where the log goes instead — each already declared, already read:
| what the session wants to keep | where |
|---|---|
| what it intended and what it got done | the PR body, ## Intent / ## Session results (session-intent) |
| what it found about a bean | a note: bun run cat beans:note <id> → beans/notes/<bean>--<date>--<branch>.md |
| a survey of a commit window it read | beans/surveys/ (session-survey) — only if it surveyed one |
| a handover to whoever comes next | a note on the bean being handed over, plus handover-report |
A handover is the same case. A Handoff: … or Handover: … bean typed
epic or milestone is a session log with a different title.
bun run cat health reports it — the bean-store check’s
bean-session-log-roots finding names every OPEN bean whose title starts
Session, Handoff or Handover and whose type is epic or milestone.
Report-only: the remedy is a person’s (or a reviewed
work-plan-restructure plan’s) — re-parent the
children by subject, move the narrative into a note, then set the log to
completed or scrapped with a pointer. Never beans delete it: other
beans, commits and PRs cite its id.
A GOAL is a milestone bean, and an epic joins one by parenting to it
Measured 2026-09-20, bean wqht. The owner had stated three goals in chat.
The store held 13 in-progress epics and 0 milestones, with the milestone
type configured and unused. No epic carried a goal’s words, so classifying 140
open items against those goals was a judgement made from scratch — and it would
have been made from scratch again at the next review, differently.
Nothing in this skill or in session-intent said where a goal LIVES. Both
describe a “goal-scoped” queue, and a queue scoped to an object that does not
exist cannot be a query; it can only be a re-derivation. The owner chose,
2026-09-20:
A goal is a
milestonebean, in the owner’s own words, and the epics serving it are parented to it.
beans create "<the goal, verbatim>" --type milestone # after the existence check above
beans update <epic-id> --parent <milestone-id>
Three properties make this the cheap answer rather than a new mechanism:
- The type already exists and
check-bean-parentsalready permits an epic under a milestone, so nothing had to be built. beans roadmapgroups byparent:, so the goals become the roadmap’s top level the moment the epics are parented — which is what a roadmap was always for.- “Prioritise against the goals” becomes a query: walk down from the milestone. An item under no goal is then visible as such, which is itself the finding a review wants.
Verbatim, and this is not a style note. A goal is the owner’s sentence, and
a tidied paraphrase is a different claim that nobody agreed to — the same rule
confirmation-waiver applies to a waiver’s quote,
and for the same reason: the reader auditing the classification has only that
string to check it against. If you do not have the owner’s words, you do not
have the goal — record the paraphrase as a paraphrase, say so, and ask.
This does not conflict with filing by subject (§”WHICH parent”). A milestone sits ABOVE the epics; a bean is still parented to the epic whose subject it is, and the epic is parented to the goal it serves. Two levels, one criterion at each.
After you create — parent it, and re-run the guard before you push
check-bean-parents fails on an open bean with no parent, because such a
bean lands in the roadmap’s Miscellaneous section where nobody looks for it. The
guard works. The way it gets past you is procedural, and it happened twice in
one session on 2026-09-20:
A multi-flag
beans updatecan apply only some of its flags.beans update <id> -s in-progress --parent <epic>set the status, silently left the parent unset, and reported success. As two calls it worked. So after setting several properties at once, read the front matter back —grep -n "^status:\|^parent:\|^priority:" beans/defs/<file>.md— rather than trusting the “Updated …” line.A bean created after your last test run is an unguarded bean. The second occurrence was not the CLI at all: the bean was minted, its body written and its priority set after
bun testhad passed, and the push went out on that stale green. CI caught it, which cost a cycle for a one-line fix.
So: beans create and the --parent that follows it are one action, and
touching the bean store invalidates your last test run. Re-run at least
bun test cat-harness-tools/scripts/tests/check-bean-parents.test.ts — 105 ms —
before pushing.
The general shape is worth more than the bean case: a green suite is green for
the tree you ran it against. Anything added afterwards, including an artefact
that is “just a note”, is untested. This is the cheap end of the same discipline
continual-progress states about verifying rendered work rather than describing
it.
A new bean also owes readme:subgraphs — and this is the most-missed step
beans/README.md is GENERATED and carries per-subdirectory counts, so creating
or archiving a bean makes it stale. Nothing in the beans CLI regenerates it,
and the gate that catches it is named for the generic case rather than for
beans:
Repository gates (hard) · step 65: "Every declared directory's README is current"
That is readme:subgraphs:check. Run its writer before you push:
bun run cat readme:subgraphs # updates beans/README.md; push state via branch-store
bun run cat readme:subgraphs:check # must exit 0
bun cat-harness-tools/scripts/branch-store.ts push --id beans # or: bun run cat state:push
Measured 2026-10-03: three separate PRs failed this gate on the same day — one for a new bean, one for a new bean on a dispatched agent’s branch, and one for a newly declared subgraph directory. The second is the instructive case: that agent pushed four consecutive commits to fix it, every one editing the bean’s prose, because the check’s name says “declared directory’s README” and gives no hint that adding a bean is what dirtied it. Sixteen of its seventeen checks were green the whole time.
So the step is cheap and the DIAGNOSIS is expensive, which is the argument for
putting it here rather than leaving it to be rediscovered: a session that reads
only the failing check name will look at directories and never at the bean it
just created. Read the failing STEP, and remember that beans/ is a declared
directory like any other.
The same shape applies one door along: editing a skill owes
bun run cat skill:register, which skill-registration carries in full. Both are
instances of one rule — a declared artefact has generated neighbours, and the
CLI that writes the artefact does not write them.
When the beans CLI is not there — you are still not read-only
scripts/beans-fallback.ts writes the same store in the same layout: same
files, same front matter, same id shape, read from .beans.yml. The CLI reads
everything it writes once installed. There is no import step and no second
store.
bun run cat beans:fallback list --status todo
bun run cat beans:fallback show <id>
bun run cat beans:fallback claim <id>
bun run cat beans:fallback create "<title>" --status in-progress
bun run cat beans:fallback note <id> "<what you found>"
A read-only fallback is not a fallback for an agent — it lets you see the plan and touch nothing, and a session that can see its plan but not claim it does its work unclaimed. That is not hypothetical: it is what happened on 2026-09-18 across two merged PRs.
Its create refuses an exact duplicate title and names the bean to claim
instead. The CLI’s create does not, and that is the mechanism behind the
14,688 duplicates in qou. --force exists for a genuinely intended duplicate
and has to be typed.
Check before you WORK — a checkbox is a claim, not a measurement (STRICT)
beans create is not idempotent, and §”Check before you create” above is the
guard. This is its twin at the other end: a bean’s stated state is a claim
somebody wrote down once, and the code has moved since.
Re-measure a bean’s open items against the code before you act on them.
The cost is not wasted effort. It is acting on a stale claim, and twice in one session (2026-09-20) that came within a single edit of re-introducing a defect a test had been written to catch.
Measured, one session, four beans
| bean | what the file said | what was true |
|---|---|---|
sa8y | four items open | one open; two done, one a deliberate decision |
vo9d | five items open | one open; four done by siblings |
30hn | 16 of 33 undeclared | 14 of 41 — and the mover was this session |
koth / b11x | two beans | one defect, filed 3m53s apart |
The two failures this prevents, and they are different
1. Doing work that exists. The cheap one. vo9d’s four items were done by
11d43ce4fb and ca5ec3e373, neither of which ticked a box.
2. Undoing a decision that was recorded elsewhere. The expensive one.
sa8y’s “the three unbound lanes get roles” reads like a gap. The roles
exist; what is missing is lanes, and its absence is deliberate —
bootstrap/scenarios/roles.json carries a _lanes_comment explaining that
binding them mints three dangling links in the root’s graph, with bean pve3
owning the question. Doing the obvious thing would have re-created the exact
shape of the wrong fix sa8y exists to record.
The decision was never in the bean. It was in the file the bean is about.
What “re-measure” means, concretely
- Run the thing.
sa8y’s headline finding was fixed hours earlier;kg:auditsays so in one command. - Read the body, not just the boxes.
vo9ddescribes a Tool node its own checkbox still shows unticked. - Date it.
git log -Son the symbol or the file against the bean’screated_atseparates fixed since from wrong when written — andvo9dwas the second: it listedcheck-l1-complete.tsas having no callers wheningest-document.tshad imported from it 32 minutes earlier. - Read the file the bean is about, for a comment saying why it is the way it is. That is where a deliberate absence lives.
And a bean you re-measure, you record
Leave what you found in the bean, with the commits and the times — a stale
checkbox you silently worked around is one the next agent meets unchanged.
Tick what is done and name who did it; withdraw a done-when you no longer
believe, with reasons, rather than leaving it unmet. Where the bean and the
code disagree, the code is what is true and the bean is what is wrong —
the same rule AGENTS.md states for a skill against
that file.
This is the work-plan half. The cross-session half — why two sessions can file
one defect four minutes apart — is
bean-coordination §”A claim is branch-local”.
Working with Beans
1. Finding Tasks & Graph Analytics (beans:query / bean_query)
Use beans list for simple listing, or bun run beans:query (and the bean_query MCP tool) for fast in-memory SPARQL 1.1 analytics over the bean store (loads ~1,000 beans into an Oxigraph store in ~70 ms):
bun run beans:query --named safe_drain_candidates: leaves with >= 2 open siblings under their parent container (safe againstcheck:bean-rollup)bun run beans:query --named actionable_leaves: open/todo leaf beans with zero open blockers, ready for an agent to claim immediatelybun run beans:query --named critical_path_blockers: blockers ranked by how many open beans they directly hold upbun run beans:query --named epic_burndown: per-epic child status breakdown (total, open, completed, scrapped, and completion percentage)bun run beans:query --named rollup_invariant_violations: completed containers with open children, or in-progress containers with 0 open childrenbun run beans:query --named stale_claims: in-progress beans whoseupdated_attimestamp is older than 7 daysbun run beans:query --named unparented_open_tasks: open non-epic beans that have no epic or milestone parentbun run beans:query --named circular_blockers: circular blocking deadlocksbun run beans:query --named high_fanout_epics: epics with high fanout (>= 15 direct children) that may need decomposition- Arbitrary SPARQL:
bun run beans:query --sparql "SELECT ?id ?title WHERE { ?b bean:id ?id ; bean:title ?title }" - Export RDF graph:
bun run beans:query --export-nq beans.nq
2. Setting Dependencies
You can map out sequence blockers using:
beans update <id> --blocked-by <blocker-id> beans update <id> --blocking <blocked-id>
3. Updating Status & Adding Comments
- When starting work:
bun run cat beans:claim <id>— a claim goes through the claim tool, never throughbeans update, so it is visible to the next session’s check (beanc3d7) - When completed:
beans update <id> --status completed - To add notes or discussion:
beans update <id> --body-append "Your note", or--body-append -with a heredoc when it runs to paragraphs. Never--body-file— it replaces the whole body, silently, exit 0: §”Check before you UPDATE”.
4. A bean that delivers a requirement copies its success criteria
When a bean is part of a work plan — it delivers one or more req: statements
(or a plan’s REQ-### / FR-###) — it names them in its body and its
## Done when is copied from their success criteria, one checkbox per
criterion, with the criterion’s key (issue #2405 FR-007). Closing the bean is
then judging those criteria. The bean ids are also listed in the requirement
set’s workPlan — a set at planned with none is refused by
check:requirements (issue #2405, FR-010/FR-012). The rule and an example are in the content layer’s
the requirement-definition skill (skill_fetch requirement-definition)
§”The work plan is beans”.
Archiving — two dispositions, and they answer different questions
beans archive moves every completed or scrapped bean out of the working
set. It moves, never deletes: an archived bean is still resolvable by id,
which is what makes it a disposal an agent may perform at all
(deletion-requires-confirmation).
There are two ways it can be driven, they are not alternatives, and a process author needs to know which one they are reaching for. Owner, 2026-09-20: “outline both options and why used for. that is part of the skills to explain in context of larger process.”
| a per-activity op | a periodic sweep | |
|---|---|---|
| what it is | a step inside one process — a fourth <cat-harness.processes:bean op> beside claim, note, resolve | a scheduled run over the whole store |
| the question it answers | is this item’s work over? | is the store still readable? |
| what decides | the process reaching a step that means completion | a uniform, process-independent criterion — completed or scrapped |
| what the archive then records | why — “archived because the release shipped” | when — “archived in the sweep of that date” |
| what it cannot do | reach a bean no process resolved | know that this release is what terminated this item |
Why neither replaces the other
The op cannot cover the store. A bean resolved by a process with no archive step, by hand, or by a sibling session, is never reached. Coverage depends on every path having been drawn, and paths are added faster than they are wired.
The sweep cannot carry authority. It knows only status. It cannot say
that a requirement was archived because the stakeholder signed off, which
is the fact an audit of the CRDM close would want — and crdm-close.bpmn is
precisely where that authority exists.
So the honest shape is: the sweep is the floor, the op is the exception. The sweep guarantees the store stays readable whatever anyone forgot; an op is worth adding only where a process’s completion is itself the reason, and recording that reason is worth the extra edge on the diagram.
Where this sits against resolve — two layers, not two spellings
resolve is the process saying “I am done with this item.” Archiving
is the store saying “this is no longer in the working set.” They are
different layers and they are allowed to be far apart in time — which is why
resolve must never imply an archive.
That gap is exactly what produced the backlog. Measured 2026-09-20: every
process that touches the work plan reaches resolve and stops —
draft-to-publication (claim → note → resolve), crdm-close (resolve),
code-change-review (claim → resolve). resolve sets completed, and
completed is what beans archive moves. Each of them manufactured the
condition; none discharged it; 219 beans accumulated until the owner
asked for the sweep by hand.
Neither disposition is built yet, and the first question is which —
bean folio-assistant-m8gz, analysis in
cat-harness/docs/proposals/bean-archiving-in-bpmn.md. Until then archiving is the
owner’s word and beans archive, run deliberately. Note the CLI prints
.beans/archive/ but honours path: from .beans.yml; here that means
beans/defs/archive/.
Status Display Format
When the user asks “status” or “show beans”, run beans list and display the
beans this session touched under their subject epics — the epic is the
heading because it is where the work lives; the session is a filter, not a
node:
## Beans this session touched (branch <branch-name>)
- [abcd] PROCESS: how an agent decides what it is doing (epic)
- [task-124] Task A (completed)
- [task-125] Task B (in-progress)
- [efgh] QA: verdicts, sidecars and the audited review record (epic)
- [task-126] Task C (todo)
Coordination discipline
These four were carried only by .claude/skills/local/todo-manager.md until
2026-09-19 — a copy that skill_fetch does not serve — so an agent asking for
this skill by name never received them. Ported here as part of bean tdmg.
- Claim before you work. Mark the bean
in-progressso sibling sessions working the same goal do not duplicate the effort — and know what that buys you. The claim lives on YOUR branch, so it is invisible onorigin/mainuntil your PR exists; it announces the item is taken, it does not reserve it. So alsogit fetch origin mainand check the open PR list for the bean id before you start. Two sessions claimed one bean 61 seconds apart on 2026-09-19 and shipped two PRs for it. Full rule:bean-coordination.md§”A claim is branch-local”. Brief it in the same turn you claim it —opening-brief.md. The claim tells a sibling the item is taken; the brief tells them, and the author, what it is being taken for. - One source of truth per concern. Do not fork a bean into a parallel
todos/*.jsonqueue; link to the queue from the bean instead. - Move wiring and script together. When relocating a hook-backed script,
move its hook reference in the same change — a script without its wiring, or
a hook reference without its script, is the migration failure mode that left
dangling references behind (
docs/folio-assistant-migration.md§2). - Close on landing. When the work lands, close the tracking bean and update any cross-repo ownership note.
Relationship to other surfaces
bean-coordination.md— the cross-session claim and handoff lifecycle, and why a claim is not a lock.bean-blocking.md— a real block carries what it waits on, since when, an expiry and a handoff.scripts/session-start-coord-sweep.sh— the CLI-independent session-start surface: fetchesorigin/mainand summarises sibling branch activity. Works even when thebeansCLI is absent.cat-harness-tools/scripts/install-beans.sh— provisions the CLI.
Processes that run this skill
| process | step(s) that name it |
|---|---|
| CRDM Phase 5 — beans and sign-off | Phase 5: Create beans |
| Agent bean lifecycle | Check before you create (exact-title search); Create the bean (agent CLI, not an engine op); Work, keeping the body current (this is ‘edit’); Complete (no unchecked todos left); Scrap with reasons NEVER delete |
| Code change and review | Record what was done, and close |
| Is the incremental IG AST what a full build would have produced? | Note the missed coupling on the bean |
| Incremental IG build | Log the environment error on the bean; Log findings on the bean; File QC findings as beans |
| L3 FHIR IG pipeline | File QC findings as beans |
| Getting started | Seed the work plan |
| Authoring a document | 2 · Seed the work plan |
| Content Change and Review | Open the branch-watch bean; Note the main-branch watch |
| Content lifecycle | Seed the work plan; File feedback as beans |
| Draft, review and publish | Open or claim the release bean; Open beans for the change requests; Close the release beans; Archive the release bean |
| Editing and HCI validation | Claim or open the bean; Log findings on the bean; Resolve or re-open the bean |
| Evidence for a recommendation | Open a bean for the unverified citation; Record the evidence gap |
| L1 document ingestion — a document to the L1 source knowledge graph | Record the gap as a bean |
| Authoring a paper | 2 · Seed the work plan |
| L2 component edit and review | Reopen upstream, flag derived assets |
| L2 DAK authoring | Seed the work plan |
| Phase 1: Planning | Assign work and begin sprint cycle |